python 爬虫获取知乎话题

3,898 阅读4分钟
原文链接: www.akiyamayzw.com

1.分析

昨天突然间好奇知乎上面到底有多少的话题数目,近期又在学习python,所以想通过python来爬一下知乎的话题数量。
第一步当然试找到知乎的话题页面知乎话题广场
2016-02-23 14:11:52屏幕截图
可以看到里面的有话题分类,每个分类下面又有子话题。如果单单使用网页的解析的方式,只能解析当前页面数据。
所以我们可以通过抓取其请求的形式来分析一下,我们使用火狐浏览器的fixbug来分析:
2016-02-23 14:20:38屏幕截图
上图可知,它是通过请求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. 获取话题

通过分析页面元素可知

2016-02-23 14:37:26屏幕截图

  • 互联网
  • 每一个标签都含有一个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来获取

    运行结果是:

    2016-02-23 15:04:03屏幕截图
    总共话题数量为:15884个

    5.待改进的地方

    可以通过一下几点来改进爬虫的性能

    • 使用多线程来爬取知乎话题。每一话题开启一个线程来获取数据,这样可以大大减少爬取数据的总时间。
    • 保存每次获取cookie的数据,动态更新cookie值