知乎爬虫

前言

知乎是一个有趣的网站,当前(2016.11.05) Alexa 全球排名为 112 ,中国排名为 24 ,从趋势看排名仍会上升,相对活跃的用户群为知乎沉淀了大量的数据,是否可以转化这些数据来做一些有趣的东西就看大家的想象力了。

想必很多人都爬取过知乎,知乎的反爬虫机制也越来越强,攻防双方都在不断地升级。这篇文章只涉及到登录知乎,完成后再拓展其他功能。

Python库

  • Requests :HTTP for Humans
  • Pillow :Python 图像处理库
  • Chrome开发者工具:获取 HTTP 通信过程

获取登录参数

打开知乎登录页面,可以看到有手机号和邮箱为账号名 2 种登录方式,尝试输入手机号、邮箱分别从 Network 控制台查看;记得勾上Preserve log,否则会看不到发送账号、密码的请求。

手机号登录 手机号Form Data 邮箱登录 邮箱Form Data

从图上可以看到:

手机号登录的请求 URL :https://www.zhihu.com/login/phone_num

password、phone_num 分别为密码、手机号

邮箱登录的请求 URL :https://www.zhihu.com/login/email

password、email 分别为密码、邮箱

但它们都有一个参数 _xsrf 的值来源不明,看名字很容易猜想是一个防止跨站请求伪造的参数,在点击“登录”按钮前就已经产生,根据它的值在页面的 HTML 文件中可以搜索到:

xsrf

获取 _xsrf 的函数:

1
2
3
4
5
6
7
8
9
def get_xsrf():
"""
获取_xrsf
"""
response = session.get('https://www.zhihu.com', headers=headers)
html = response.text
pattern = re.compile(r'<input type="hidden" name="_xsrf" value="(.*?)"/>')
_xsrf = re.findall(pattern, html)
return _xsrf[0]

至此所有的登录参数都找到了,根据这些参数发送登录请求,会返回类似这样的响应:

1
2
3
4
5
6
7
8
9
10
{
"r": 1,
"errcode": 100000,
"data": {"account":"\u5e10\u53f7\u6216\u5bc6\u7801\u9519\u8bef"},
"msg": "\u8be5\u624b\u673a\u53f7\u5c1a\u672a\u6ce8\u518c\u77e5\u4e4e"
}

对比成功登录和登录失败,发现返回"r": 0表示登录成功,"r": 1表示登录失败。然而尝试了几次登录之后,后面的登录都失败了,从浏览器中发现有时会出现验证码的情况,仅仅发送前面发现的几个参数还不够,从控制台中看到英文、中文验证码分别对应下面 2 种 Form Data:

英文验证码

参数值就是验证码中的英文字母

中文验证码 验证码分辨率 中文验证码Form Data

增加一个参数值captcha_type: cncaptchaimg_size分别是图片横纵分辨率的 1/2 ,input_points是点击的标记在图中坐标系中的坐标。

获取验证码

从控制台中查看获取验证码的请求:

获取验证码

可以看到验证码的请求 URL :https://www.zhihu.com/captcha.gif?r=1478334713478&type=login ,并且每次刷新验证码的请求 URL 都不一样,Google 求救后发现r后面的参数是一个时间戳。

时间戳是指格林尼治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

无论是英文字母还是倒立中文的验证码,识别起来都比较困难,暂时只能采用人工识别的方式输入验证码,自动识别验证码可以在以后学习图像处理时挑战一下。

获取验证码的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def get_captcha():
"""
获取验证码
"""
t = int(time.time() * 1000)
# 知乎网页版登录界面有时出现的是点击倒立中文字的验证模式
# 请求URL是在 type=login 后面加上 &lang=cn 参数
# 这里强制请求输入字母类型的验证码,就不涉及到点击了
captcha_url = 'http://www.zhihu.com/captcha.gif?r=%s&type=login' % t
response = session.get(captcha_url, headers=headers)
with open('captcha.jpg', 'wb') as f:
f.write(response.content)
f.close()
image = Image.open('captcha.jpg')
image.show()
captcha = input('请输入验证码:')
return captcha

登录知乎

利用上面登录参数成功登录后,后面对知乎的访问都使用 cookies 进行,为了方便直接采用 requests 的 会话对象来保持 cookies :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
session = requests.Session()
def test_login():
"""
测试是否登录成功,并显示欢迎词
此函数非必须
"""
url = "https://www.zhihu.com/"
response = session.get(url, headers=headers, allow_redirects=False)
html = response.text
pattern = re.compile(r'<span\sclass="name">(.*)</span>')
name = re.findall(pattern, html)[0]
print('%s , Welcome back to Zhihu!' % name)
print()
print('首页内容标题:')
pattern2 = re.compile(r'<a\sclass="question_link"\shref="/question/[0-9]{1,}#answer-[0-9]{1,}"\starget="_blank"\sdata-id="[0-9]{1,}">\n(.*)\n</a>')
question = re.findall(pattern2, html)
for title in question:
print(title)

返回的结果:

首页内容标题

项目地址

GitHub - wish007/zhihu