技术科普

知乎数据爬取

2018-05-15 13:53:19 35

命令行执行scrapy shell  获取知乎内容

scrapy shell -s USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36" https://www.zhihu.com/question/39808733

这里特别注意(我们都知道知乎必须每次都发送USER_AGENT,才能访问),所以这里必须带上USER_AGENT,否则知乎返回的是500错误或者400错误,正确代码如上。

这样我们看到返回200页面

image.png

实战:

for url in self.start_urls:
    yield scrapy.Request(url, dont_filter=True, headers=self.headers)

我们上面的代码是没有回调函数callback的,所有获取到后直接跳转到parse函数进行处理

def parse(self, response):
    """
    提取出html页面中的所有url 并跟踪这些url进行一步爬取
    如果提取的url中格式为 /question/xxx 就下载之后直接进入解析函数
    """
    # 提取页面所有url
    all_urls = response.css("a::attr(href)").extract()
    all_urls = [parse.urljoin(response.url,url) for url in all_urls]

这里我们提取到的url是question/39808733,不是我们想要的完整的地址,这个时候可以使用一个函数urljoin来自动拼接我们的url,他会自动将我们提取到的url跟提取页面也就是response.url进行自动拼接。

all_urls = [parse.urljoin(response.url,url) for url in all_urls]

当然它是在urllib里的,兼容写法为:

try:
    import urlparse as parse
except:
    from urllib import parse
def parse(self, response):
        """
        提取出html页面中的所有url 并跟踪这些url进行一步爬取
        如果提取的url中格式为 /question/xxx 就下载之后直接进入解析函数
        """
        all_urls = response.css("a::attr(href)").extract()
        all_urls = [parse.urljoin(response.url, url) for url in all_urls]
        all_urls = filter(lambda x:True if x.startswith("https") else False, all_urls)
        for url in all_urls:
            match_obj = re.match("(.*zhihu.com/question/(\d+))(/|$).*", url)
            if match_obj:
                #如果提取到question相关的页面则下载后交由提取函数进行提取
                request_url = match_obj.group(1)
                yield scrapy.Request(request_url, headers=self.headers, callback=self.parse_question)
            else:
                #如果不是question页面则直接进一步跟踪
                yield scrapy.Request(url, headers=self.headers, callback=self.parse)

以上代码说明:

all_urls里包括可能不是https的链接,所以需要过滤,这里我们使用filter函数,lambda函数自己百度使用方法,:我们对all_urls里的每个url也就是x进行过滤,当满足是https时保留,不是时返回False。

这里还可以直接用if判断:

for url in all_urls:
    if url.startswith("https"):
    下面是逻辑...


以上部分代码省略,接下来直接进行知乎问题处理:

def parse_question(self, response):
    # 处理question页面, 从页面中提取出具体的question item
if "QuestionHeader-title" in response.text:
        # 处理新版本
match_obj = re.match("(.*zhihu.com/question/(\d+))(/|$).*", response.url)
        if match_obj:
            question_id = int(match_obj.group(2))

        item_loader = ItemLoader(item=ZhihuQuestionItem(), response=response)
        item_loader.add_css("title", "h1.QuestionHeader-title::text")
        item_loader.add_css("content", ".QuestionHeader-detail")
        item_loader.add_value("url", response.url)
        item_loader.add_value("zhihu_id", question_id)
        item_loader.add_css("answer_num", ".List-headerText span::text")
        item_loader.add_css("comments_num", ".QuestionHeader-actions button::text")
        item_loader.add_css("watch_user_num", ".NumberBoard-value::text")
        item_loader.add_css("topics", ".QuestionHeader-topics .Popover div::text")

        question_item = item_loader.load_item()
    else:
        # 处理老版本页面的item提取
match_obj = re.match("(.*zhihu.com/question/(\d+))(/|$).*", response.url)
        if match_obj:
            question_id = int(match_obj.group(2))

        item_loader = ItemLoader(item=ZhihuQuestionItem(), response=response)
        # item_loader.add_css("title", ".zh-question-title h2 a::text")
item_loader.add_xpath("title",
                              "//*[@id='zh-question-title']/h2/a/text()|//*[@id='zh-question-title']/h2/span/text()")
        item_loader.add_css("content", "#zh-question-detail")
        item_loader.add_value("url", response.url)
        item_loader.add_value("zhihu_id", question_id)
        item_loader.add_css("answer_num", "#zh-question-answer-num::text")
        item_loader.add_css("comments_num", "#zh-question-meta-wrap a[name='addcomment']::text")
        # item_loader.add_css("watch_user_num", "#zh-question-side-header-wrap::text")
item_loader.add_xpath("watch_user_num",
                              "//*[@id='zh-question-side-header-wrap']/text()|//*[@class='zh-question-followers-sidebar']/div/a/strong/text()")
        item_loader.add_css("topics", ".zm-tag-editor-labels a::text")

        question_item = item_loader.load_item()

    yield scrapy.Request(self.start_answer_url.format(question_id, 20, 0), headers=self.headers,
                         callback=self.parse_answer)
    yield question_item

说明:

ItemLoader需要引入:(同时需要引入知乎的两个Item)

from scrapy.loader import ItemLoader
from items import ZhihuQuestionItem,ZhihuAnswerItem

item_loader = ItemLoader(item=ZhihuQuestionItem(), response=response)

这个位置传递的是知乎的Item和返回的response