1.分析
昨天突然间好奇知乎上面到底有多少的话题数目,近期又在学习python,所以想通过python来爬一下知乎的话题数量。
第一步当然试找到知乎的话题页面知乎话题广场
可以看到里面的有话题分类,每个分类下面又有子话题。如果单单使用网页的解析的方式,只能解析当前页面数据。
所以我们可以通过抓取其请求的形式来分析一下,我们使用火狐浏览器的fixbug来分析:
上图可知,它是通过请求POST接口来取得知乎话题数据,接口信息:
_xsrf 65eb6285c92b03968844d921a259af38
method next
params {"topic_id":833,"offset":0,"hash_id":"6c47827e610fa47c8d6e368e88578fc6"}
接口地址https://www.zhihu.com/node/TopicsPlazzaListV2 其中topic_id指大分类下的id,offset是指偏移量,指每次执行next方法加载的子话题数量,hash_id可以为空我们暂时忽略它。当每次加载数据时候offset偏移20,每次切换父话题都需要改变id编号。
嗯,大致上就是这样的。我们可以通过POSTMAN模拟一下请求。
2. 获取话题
通过分析页面元素可知
互联网
每一个标签都含有一个data-id的东西,这个就代表话题的id,xxx其中xxx表示话题的名称,可以使用正则表达式来获取话题名称。我们先新建一个Topic的类:
class Topic(object):
def __init__(self, id, name):
self.id = id
self.name = name
使用urllib来获取html元素标签。
def getTopics():
zhihuTopics = []
url = 'https://www.zhihu.com/topics'
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
request = urllib2.Request(url)
response = opener.open(request)
pattern = re.compile('(.*?)',re.S)
results = re.findall(pattern,response.read().decode('utf-8'))
for n1 in results:
print n1[0],n1[1]
topic = Topic(n1[0],n1[1])
zhihuTopics.append(topic)
return zhihuTopics
其中正则表达式
(.*?)
主设置了2个变量获取,分别为话题id和话题名称。
3.获取话题下面的子话题
可以通过模拟POST请求获取对应的子话题,我们需要话题子话题的名称和简介内容,我们首先新建一个对象类Content:
class Content(object):
def __init__(self, name, content):
self.name = name
self.content = content
下面代码是模拟POST请求JSON数据的,通过解析JSON数据来获取值,并把获取到的值写道txt文件里面去
def getSubTopic(topic):
url = 'https://www.zhihu.com/node/TopicsPlazzaListV2'
isGet = True;
offset = -20;
contents = []
while isGet:
offset = offset + 20
values = {'method': 'next', 'params': '{"topic_id":'+topic.id+',"offset":'+str(offset)+',"hash_id":""}'}
try:
data = urllib.urlencode(values)
request = urllib2.Request(url,data,headers)
response = urllib2.urlopen(request)
json_str = json.loads(response.read().decode('utf-8'))
# 将获取到的数组转换成字符串
topicMsg = '.'.join(json_str['msg'])
pattern = re.compile('(.*?).*?(.*?)
',re.S)
results = re.findall(pattern,topicMsg)
if len(results) ==0:
isGet =False
for n in results:
content = Content(n[0],n[1])
contents.append(content)
print n[0],'->'+n[1]
except urllib2.URLError, e:
if hasattr(e,"reason"):
print u"错误原因",e
file = open(topic.name+'.txt','w')
wiriteLog(contents,file)
return contents
def wiriteLog(contentes,file):
for content in contentes:
file.writelines(('\n'+content.name+'->'+content.content).encode("UTF-8"))
其中正则和前面的获取标题类似。
4.完整流程
通过对每一个话题下的子话题进行爬去来获取所有的数据,完整代码如下:
# -*- coding: utf-8 -*-
__author__ = 'akiyama'
import urllib
import urllib2
import re
import json
from com.learn.zhihuSearch.topic import *
from com.learn.zhihuSearch.content import *
import cookielib
def getTopics():
zhihuTopics = []
url = 'https://www.zhihu.com/topics'
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
request = urllib2.Request(url)
response = opener.open(request)
pattern = re.compile('(.*?)',re.S)
results = re.findall(pattern,response.read().decode('utf-8'))
for n1 in results:
print n1[0],n1[1]
topic = Topic(n1[0],n1[1])
zhihuTopics.append(topic)
return zhihuTopics
def getSubTopic(topic):
url = 'https://www.zhihu.com/node/TopicsPlazzaListV2'
isGet = True;
offset = -20;
contents = []
while isGet:
offset = offset + 20
values = {'method': 'next', 'params': '{"topic_id":'+topic.id+',"offset":'+str(offset)+',"hash_id":""}'}
try:
data = urllib.urlencode(values)
request = urllib2.Request(url,data,headers)
response = urllib2.urlopen(request)
json_str = json.loads(response.read().decode('utf-8'))
# 将获取到的数组转换成字符串
topicMsg = '.'.join(json_str['msg'])
pattern = re.compile('(.*?).*?(.*?)
',re.S)
results = re.findall(pattern,topicMsg)
if len(results) ==0:
isGet =False
for n in results:
content = Content(n[0],n[1])
contents.append(content)
print n[0],'->'+n[1]
except urllib2.URLError, e:
if hasattr(e,"reason"):
print u"错误原因",e
file = open(topic.name+'.txt','w')
wiriteLog(contents,file)
return contents
def wiriteLog(contentes,file):
for content in contentes:
file.writelines(('\n'+content.name+'->'+content.content).encode("UTF-8"))
print '开始拉取数据...\n'
headers = {'User-Agent' : 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0',
'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With':'XMLHttpRequest',
'Referer':'https://www.zhihu.com/topics',
'Cookie':'__utma=51854390.517069884.1416212035.1416212035.1416212035.1; q_c1=c02bf44d00d240798bfabcfc95baeb56|1455778173000|1416205243000; _za=b1c8ae35-f986-46a2-b24a-cb9359dc6b2a; aliyungf_tc=AQAAAJ1m71jL1woArKqF22VFnL/wRy6C; _xsrf=9d494558f9271340ab24598d85b2a3c8; cap_id="MDNiMjcwM2U0MTRhNDVmYjgxZWVhOWI0NTA2OGU5OTg=|1455864276|2a4ce8247ebd3c0df5393bb5661713ad9eec01dd"; n_c=1; _alicdn_sec=56c6ba4d556557d27a0f8c876f563d12a285f33a'
}
i = 0
topics = getTopics()
for topic in topics:
content = getSubTopic(topic)
i +=len(content)
print '知乎总话题数为:'+str(i)
print '拉取数据结束'
在headers里面的Cookie有过期时间的,如果代码没有正确获取到数据,报302之类的错误一般可能试Cookie失效了,你们可以通过拷贝自己浏览器中的Cookie来获取
运行结果是:
5.待改进的地方
可以通过一下几点来改进爬虫的性能
- 使用多线程来爬取知乎话题。每一话题开启一个线程来获取数据,这样可以大大减少爬取数据的总时间。
- 保存每次获取cookie的数据,动态更新cookie值