详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据

详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据_第1张图片
开篇.jpg

环境:win7、pycharm、python3.5
关键字:scrapy1.4、mongodb

本文主要分享用scrapy1.4实现知乎模拟邮箱登陆以及将根据rules匹配的url链接爬取(详见下文zhihu_login.py)数据并存入mongodb。
在做之前阅读了这两篇文章Python爬虫(七)--Scrapy模拟登录 和Scrapy模拟登陆知乎,但是未能运行成功,文中也没有处理验证码。不过,实现原理大同小异,大家可以参考,本文主要简单实现并且正确运行,有什么问题还望大家多多指教。谢谢^^
获取源码请戳我
下面正文开始:

详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据_第2张图片
scrapy_architecture.png

1.cmd命令行下新建项目, 详情请戳我

scrapy startproject zhihu_login_byscrapy
目录结构如下:
zhihu_login_byscrapy/
    scrapy.cfg            #项目的配置文件
    zhihu_login_byscrapy/             
        __init__.py
        items.py          # 保存爬取到的数据的容器
        pipelines.py      # 项目 pipelines 文件 实现存储
        settings.py       # 项目的设置文件
        spiders/          # 这里写你的爬虫程序
            __init__.py
详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据_第3张图片
目录结构.png

2.模拟登陆并爬取数据zhihu_login.py

在运行前请将代码中的password 和email 替换

主要逻辑分为:

  • 获取_xsrf
  • 获取验证码
  • 传递入参数post请求处理
  • ItemLoader解析数据

涉及的知识可参考官方文档 +Rule使用+ItemLoader详解
完整代码及其注释如下:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.selector import Selector
from scrapy.linkextractors import LinkExtractor
from scrapy.http import Request, FormRequest
from zhihu_login_byscrapy.items import ZhihuLoginByscrapyItem
import json,time
from PIL import Image
from scrapy.loader import ItemLoader

class ZhihuLoginByscrapy(CrawlSpider):
    name = 'zhihu_scrapy'   #唯一值
    allowed_domains = ["zhihu.com"]
    start_urls = [
        "https://www.zhihu.com"
    ]
    #自动从response中根据正则表达式提取url,再根据这个url再次发起请求,并用callback解析返回的结果
    #follow修改为True,那么爬虫会start_urls爬取的页面中在寻找符合规则的url,如此循环,直到把全站爬取完毕。 
    rules = (
        Rule(LinkExtractor(allow=('/question/\d+#.*?', )),callback='parse_question',follow=True),
        Rule(LinkExtractor(allow=('/question/\d+',)), callback='parse_question', follow=True),
    )
    #请求头
    headers = {
        'Accept':'* / *',
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.8",
        "Connection": "keep-alive",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "Host": "www.zhihu.com",
        "Referer": "https://www.zhihu.com/",
    }

    #程序入口
    def start_requests(self):
        return [Request("https://www.zhihu.com/login/email",headers = self.headers,meta={"cookiejar":1},callback=self.post_login)]

    def post_login(self,response):
        #获取_xsrf
        _xsrf = Selector(response).xpath('//input[@name="_xsrf"]/@value').extract()[0]
        if _xsrf:
            formdata = {
                '_xsrf': _xsrf,
                'password': '*********',
                'remember_me': 'true',
                'email': '******@*****.com',
                'captcha': ''
            }
            t = str(int(time.time() * 1000))
            captcha_url = 'http://www.zhihu.com/captcha.gif?r=' + t + "&type=login"
            return [Request(captcha_url, headers=self.headers, meta={"cookiejar": response.meta['cookiejar'], "formdata": formdata},
                            callback=self.parse_captcha)]
     #获取验证码
    def parse_captcha(self, response):
        with open('captcha.jpg', 'wb') as f:
            f.write(response.body)
        # Pillow显示验证码
        img = Image.open('captcha.jpg')
        img.show()
        captcha = input('请需要输入验证码: ')
        formdata = response.meta['formdata']
        formdata['captcha'] = captcha
        #登陆成功后, 会调用after_login回调函数
        return [FormRequest("https://www.zhihu.com/login/email",
                            meta={'cookiejar': response.meta['cookiejar']},
                            method='POST',
                            headers=self.headers,
                            formdata=formdata,
                            callback=self.after_login,
                            dont_filter = True,
                              )]

#程序自动使用start_urls构造Request并发送请求,然后调用parse函数对其进行解析,在这个解析过程中使用rules中的规则从html(或xml)文本中提取匹配的链接,通过这个链接再次生成Request,如此不断循环,直到返回的文本中再也没有匹配的链接,或调度器中的Request对象用尽,程序才停止。
    def after_login(self,response):
        for url in self.start_urls:
            yield Request(url, headers=self.headers, meta={'cookiejar': response.meta['cookiejar']}, dont_filter=True)

    def parse_question(self, response):
        self.logger.info('Hi, this is an item page! %s', response.url)
        item = ItemLoader(item=ZhihuLoginByscrapyItem(), response=response)
        #传递 Item 类的字段名称和对应的 css 解析语法
        item.add_value('url',response.url)
        item.add_css('title','h1.QuestionHeader-title::text')
        item.add_xpath('read', '(//div[@class="NumberBoard-value"])[1]/text()')
        item.add_xpath('focus', '(//div[@class="NumberBoard-value"])[2]/text()')
        item.add_xpath('description', '//span[@class="RichText CopyrightRichText-richText"]/text()')
        print(item)
        return item.load_item()

3.在items.py中定义爬取到数据的容器,简言之就是定义爬取到的数据字段,以匹配到的此链接为例,获取问题的url、标题、关注者、浏览量、问题描述。

涉及的知识可参考input/output_processor+使用 Item 类转换传输数据以及ItemLoader 机制解析
完整代码及其注释如下(还可以优化):

import scrapy
from scrapy.loader.processors import TakeFirst,MapCompose,Join

class ZhihuLoginByscrapyItem(scrapy.Item):
    # define the fields for your item here like:
    url = scrapy.Field(
        output_processor=Join(),
    )  # 问题url
    title = scrapy.Field(
        output_processor=Join(),
    ) # 问题标题
    focus = scrapy.Field(
        output_processor=Join(),
    ) # 问题关注者有多少
    read = scrapy.Field(
        output_processor=Join(),
    ) # 问题浏览多少
    description = scrapy.Field(
        output_processor=Join(),
    ) # 问题描述
  1. pipelines.py中将数据存储到json和mongodb中
    涉及的知识可参考: 使用 Pipeline 保存数据 + 官方文档mongodb案例+JsonItemExporter参考
from scrapy.exporters import JsonItemExporter
import pymongo

class ZhihuLoginByscrapyPipeline(object):
    # 调用 scrapy 提供的 json exporter 导出 json 文件
    def __init__(self):
        self.file = open('zhihu_exporter.json', 'wb')
        # 初始化 exporter 实例,执行输出的文件和编码
        self.exporter = JsonItemExporter(self.file, encoding='utf-8', ensure_ascii=False)
        # 开启导出
        self.exporter.start_exporting()

    def close_spider(self, spider):
        self.exporter.finish_exporting()
        self.file.close()

    # 将 Item 实例导出到 json 文件
    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item

class MongoByscrapyPipeline(object):

    def __init__(self, mongo_uri, mongo_db,mongo_col):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
        self.mongo_col = mongo_col

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGODB_DB'),
            mongo_col=crawler.settings.get('MONGODB_COLLECTION')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        # 释放资源
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.mongo_col].insert_one(dict(item))
        return item

5.settings.py添加如下内容
涉及的知识可参考官方文档settings

#修改USER_AGEN
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'

DOWNLOAD_DELAY = 4 #下载延迟
#PIPELINES配置,后面数字代表优先级
ITEM_PIPELINES = {
   'zhihu_login_byscrapy.pipelines.MongoByscrapyPipeline': 300,
   'zhihu_login_byscrapy.pipelines.ZhihuLoginByscrapyPipeline': 800,
}
#mongodb配置
MONGODB_SERVER = "localhost"
MONGODB_PORT = 27017
MONGODB_DB = "ana_zhihu"
MONGODB_COLLECTION = "zhihu_scrapy"

6.以上就是涉及到的代码,下面就是pycharm中执行我们的scrapy程序,新建begin.py文件,首先需要按图示配置好,Script处指定执行脚本,最后点击执行即可~
详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据_第4张图片
配置.png

begin.py

from scrapy import cmdline
cmdline.execute('scrapy crawl zhihu_scrapy '.split())

7.最后运行效果如下
详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据_第5张图片
控制台print出的item.png
详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据_第6张图片
mongodb中存储的数据.png
详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据_第7张图片
存储的json数据.png

8.以上就是所有内容,但是尚有需要完善的地方,比如模拟手机登陆、爬虫job的暂停&执行、中间件的使用、代码优化等。

延伸阅读请戳我

你可能感兴趣的:(详解 | Scrapy1.4 知乎模拟登陆 & 爬取首页、存储数据)