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

开篇.jpg

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

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

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
目录结构.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处指定执行脚本,最后点击执行即可~
配置.png

begin.py

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

7.最后运行效果如下
控制台print出的item.png
mongodb中存储的数据.png
存储的json数据.png

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

延伸阅读请戳我

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容