爬取知乎全部话题-子话题
分析
使用Chome浏览器打开知乎的话题广场页面,可以看到页面中有话题分类,每个分类下面又有子话题,但每次默认只显示20个子话题,要获取全部的子话题,需要不断的“点击”下面的“更多”按钮,因此可以有两种方法来实现:
- 通过selenium来实现(模拟鼠标点击)
- 通过分析“点击”按钮触发时请求,模拟请求来进行抓取
selenium爬取方式速度慢,因此这里不对该方法的使用做进一步说明。此处针对模拟请求的方式来进行爬取:
首先,在浏览器界面,按下键盘F12
,选择Network
选项卡(建议勾选"Preseve log"
)。此时,点击知乎上的“互联网”话题,可以看到如下图所示的两个请求数据包:
由上图可知,当点击“互联网”话题时浏览器是通过发送POST请求
来取得知乎话题数据。该请求的url地址为https://www.zhihu.com/node/TopicsPlazzaListV2,在POST请求
的数据部分:topic_id
指话题id(此处对应“互联网”话题的话题id就是99),offset
是指偏移量,指每次执行next
方法(method:next
)加载的子话题数量,hash_id
可以为空我们直接忽略。
此时我们点击网页中的更多
来查看更多的子话题,并查看网络请求包的变化,如下图所示:
可以看出,当每次点击更多
加载数据时,在POST请求
的数据部分offset
偏移递增20(点击多次更多
可得道验证)。此外当我们切换话题时(如点击游戏
/运动
/艺术
),POST请求
的数据部分的topic_id
都会相应改变。
因此为了爬取所有的话题和子话题,我们需要做两个工作:
- 爬取所有的话题和相应的
topic_id
(爬取子话题时POST请求
的数据部分需要topic_id
) - 爬取所有的子话题,发送
POST请求
来进行抓取
爬取话题和对应的topic_id
这一步很简单,直接获取源码,然后通过xpath
解析即可:
右键查看网页源代码1
2
3
4<li class="zm-topic-cat-item" data-id="253"><a href="#游戏">游戏</a></li>
<li class="zm-topic-cat-item" data-id="833"><a href="#运动">运动</a></li>
<li class="zm-topic-cat-item" data-id="99"><a href="#互联网">互联网</a></li>
...
可知,所有的话题和topic_id
都在class="zm-topic-cat-item"
的<li>
标签中,因此,话题和topic_id
爬取代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import requests
from lxml import etree
session = requests.session()
headers = {
"User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/56.0.2924.76 Chrome/56.0.2924.76 Safari/537.36"
}
req = session.get("https://www.zhihu.com/topics", headers=headers)
selector = etree.HTML(req.text)
topicList = selector.xpath('//li[@class="zm-topic-cat-item"]')
#print len(topicList)
topicIDList = []
topicNameList = []
for topic in topicList:
topicIDList.append(topic.xpath('./@data-id')[0])
topicNameList.append(topic.xpath('./a/text()')[0])
topicDict = dict(zip(topicIDList, topicNameList))
最后的topicDict
就是话题和对应的topic_id
。
爬取子话题
比较麻烦的就是子话题的抓取。如1.分析
中的分析,我们要构建POST请求
,主要是其中的数据部分:1
2
3
4data = {
"method":"next",
"params": '{"topic_id":' + str(topic_id) + ',"offset":' + str(offset) + ',"hash_id":""}'
}
在2.爬取话题和对应的topic_id
中我们有了全部的topic_id
,offset
每次递增20,因此POST请求
的数据部分也构建完毕,我们查看能够得到我们想要的数据,得到的响应数据如下:1
2
3
4
5
6
7
8
9{
"r":0,
"msg":[
"<div class="item"><div class="blk"> <a target="_blank" href="/topic/19550757"> <img src="https://pic3.zhimg.com/127ee131a4487388e104da2bba7a4df6_xs.jpg" alt="腾讯"> <strong>腾讯</strong> </a> <p>中国最大的互联网综合服务提供公司,主营以腾讯网、QQ、微信、腾…</p> <a id="t::-176" href="javascript:;" class="follow meta-item zg-follow"><i class="z-icon-follow"></i>关注</a> </div></div>",
"<div class="item"><div class="blk"> <a target="_blank" href="/topic/19854644"> <img src="https://pic1.zhimg.com/e9c699fa4_xs.jpg" alt="余额宝"> <strong>余额宝</strong> </a> <p>余额宝创立于2013年6月,是蚂蚁金服旗下的一项余额增值服务和…</p> <a id="t::-103570" href="javascript:;" class="follow meta-item zg-follow"><i class="z-icon-follow"></i>关注</a> </div></div>",
"<div class="item"><div class="blk"> <a target="_blank" href="/topic/19551460"> <img src="https://pic3.zhimg.com/e75e39ed2_xs.jpg" alt="百度"> <strong>百度</strong> </a> <p>中国互联网公司之一,占有中国搜索引擎市场五成以上的份额。旗下有…</p> <a id="t::-413" href="javascript:;" class="follow meta-item zg-follow"><i class="z-icon-follow"></i>关注</a> </div></div>",
...
]
}
显然我们关注的数据都在"msg"
字段中,并且"msg"
字段对应的value是一个字符串数组(字符串内容是一个xml
格式文本,可以直接使用xpath
进行提取)。
至此,还有一个问题,每次offset
递增20,什么时候不再递增了呢?通过进一步调试我们可以发现当sourceCodeDict["msg"] == []
时表示当前话题的子话题都提取到了。
完整代码
1 | #!/usr/bin/env python |