Scrapy详解 爬虫框架入门看这一篇就够了!

前言

学习Scrapy有一段时间了,当时想要获取一下百度汉字的解析,又不想一个个汉字去搜,复制粘贴太费劲,考虑到爬虫的便利性,这篇文章是介绍一个爬虫框架--Scrapy,非常主流的爬虫框架,写爬虫还不会Scrapy,你就out啦🙈~

🐞爬虫的应用场景:

  • 搜索多个汉字,存储下来汉字的解析
  • 每隔一段时间获取一下最新天气,新闻等等
  • 拿到豆瓣电影(豆瓣图书)的top100的电影名字、演员、上映时间以及各大网友的评论
  • 需要下载网站的一系列图片,视频等,下载慕课网的课程视频
  • 搜集安居客的所有房源,性价比分析
  • 刷票、抢票
  • 拿到微博当前的热门话题,自媒体需要即时写文章啦
  • ...

架构

官方解析:Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。

其最初是为了页面抓取(更确切来说,网络抓取)所设计的,也可以应用在获取API所返回的数据或者通用的网络爬虫。

Scrapy架构图.png

架构分析

  • Scrapy Engine:Scrapy引擎。负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。
  • Scheduler:调度器。从Scrapy Engine接受请求(requests)并排序列入队列,并在引擎再次请求时返回。用它来决定下一个抓取的网址是什么,同时去除重复的网址。
  • Downloader:下载器。抓取网页并将网页内容返还给Spiders。建立在twisted异步模型。
  • Spiders:爬虫。用户自定义的类,主要用来解析网页,提取Items,发送url跟进等新请求等。
  • Item Pipelines:管道。主要用来处理Spider解析出来的Items,进行按规则过滤,验证,持久化存储(如数据库存储)等
  • Downloader Middlewares:下载中间件。位于Scrapy Engine和Downloader之间,主要是处理Scrapy引擎与下载器之间的请求及响应。
  • Spider Middlewares:爬虫中间件。位于Scrapy Engine和Spiders之间,主要工作是处理Spiders的响应输入和请求输出。
  • Scheduler Middlewares:调度中间件。位于Scrapy Engine和Scheduler之间。主要工作是处理从Scrapy Engine发送到Scheduler的请求和响应。

数据处理流程
1、引擎打开一个网站(open a domain),找到处理该网站的Spider并向该Spider请求要爬取的第一个start_urls。
2、引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
3、引擎向调度器请求下一个要爬取的URL。
4、调度器返回下一个要爬取的URL给引擎,引擎将URL通过Downloader Middlewares(request)转发给下载器(Downloader)。
5、一旦页面下载完毕,Downloader生成一个该页面的Response,并将其通过Downloader Middlewares(response)发送给引擎。
6、引擎从Downloader中接收到Response并通过Spider Middlewares(request)发送给Spider处理。
7、Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
8、引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
9、系统重复2-9的操作,直到调度中没有更多地request,然后断开引擎与网站之间的联系。

安装

依赖环境:

  • Python 2.7及以上
  • Python Package: pip and setuptools. 现在 pip 依赖 setuptools ,如果未安装,则会自动安装 setuptools 。

使用pip安装:

pip install Scrapy

创建项目:

scrapy startproject [项目名]

如创建 scrapy startproject qimairank,会自动创建Scrapy的项目架构:

qimairank

|--qimairank
    |--spiders
        |--__init__.py
    |--__init__.py
    |--items.py
    |--middlewares.py
    |--pipelines.py
    |--settings.py
|--scrapy.cfg
  • scrapy.cfg:项目的配置文件,指定settings文件,部署deploy的project名称等等。
  • qimairank:项目的python模块。
  • spiders:放置spider代码的目录。
  • items.py:项目中的item文件。
  • pipelines.py:项目中的pipelines文件。
  • middlewares.py:项目的中间件。
  • settings.py:Scrapy 配置文件。更多配置信息查看:https://scrapy-chs.readthedocs.io/zh_CN/0.24/topics/settings.html

第一个爬虫:爬取有道翻译

熟悉Scrapy框架后,我们手写第一个爬虫,爬取有道翻译的单词发音,发音文件链接,释义,例句。

如单词proportion:有道翻译的详情连接为 http://dict.youdao.com/w/eng/proportion 。本篇文章爬取的内容结果:

{"example": [{"en": "I seemed to have lost all sense of proportion.",
              "zh": "我好象已经丧失了有关比例的一切感觉。"},
             {"en": "The price of this article is out of(all) proportion to its value.",
              "zh": "这个商品的价格与它的价值完全不成比例。"},
             {"en": "But, the use of interception bases on the violation of the citizen rights, so it should be satisfactory of the principle of legal reservation and the principle of proportion.",
              "zh": "但是,监听的适用是以侵害公民权利为前提的,因此监听在刑事侦查中的运用必须满足法律保留原则和比例原则的要求。"}],
 "explain": ["n. 比例,占比;部分;面积;均衡", "vt. 使成比例;使均衡;分摊"],
 "pron": "[prə'pɔːʃ(ə)n]",
 "pron_url": "http://dict.youdao.com/dictvoice?audio=proportion&type=1",
 "word": "proportion"}
proportion有道释义.jpg

创建项目

在需要创建的目录下,

scrapy startproject youdaoeng

回车即可创建默认的Scrapy项目架构。

youdaoeng项目架构.jpg

创建Item

创建YoudaoengItem继承scrapy.Item,并定义需要存储的单词,发音,发音文件链接,释义,例句。

import scrapy


class YoudaoengItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 单词
    word = scrapy.Field()
    # 英式发音
    pron = scrapy.Field()
    # 发音audio文件链接
    pron_url = scrapy.Field()
    # 释义
    explain = scrapy.Field()
    # 例句
    example = scrapy.Field()

创建Spider

spiders目录下创建EngSpider.py,并创建class EngSpider,继承于Spider。

from scrapy import Spider


class EngSpider(Spider):
    name = "EngSpider"
    # 允许访问的域
    allowed_domains = ["dict.youdao.com"]

    start_urls = [
        'http://dict.youdao.com/w/eng/agree', 'http://dict.youdao.com/w/eng/prophet',
                'http://dict.youdao.com/w/eng/proportion']

    def parse(self, response):
        pass
  • name:用于区别Spider,该名字必须是唯一的。
  • start_urls:Spider在启动时进行爬取的url列表,首先会爬取第一个。
  • def parse(self, response):得到请求url后的response信息的解析方法。

有道翻译的网址为http://dict.youdao.com/ ,根据分析,查询英文单词结果后链接更改,如查询agree,跳转单词详情地址为http://dict.youdao.com/w/eng/agree 。所以几乎可以认为单词的详情页链接可以是http://dict.youdao.com/w/eng/ 拼接上单词本身,所以配置start_urls我们查询三个单词的释义详情。

解析

解析用的Selectors选择器有多种方法:

  • xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
  • css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.
  • extract(): 序列化该节点为unicode字符串并返回list。
  • re(): 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。

下面我们用xpath()选择节点,xpath的语法可参考w3c的http://www.w3school.com.cn/xpath/xpath_nodes.asp 学习,需要熟悉语法、运算符、函数等。

    def parse(self, response):
        box = response.xpath('//*[@id="results-contents"]')
        word = YoudaoengItem()
        # 简明释义
        box_simple = box.xpath('.//*[@id="phrsListTab"]')
        # 判断查出来的字是否存在
        if box_simple:
            # 单词
            word['word'] = box_simple.xpath('.//h2[@class="wordbook-js"]//span[@class="keyword"]/text()').extract()[0]
            # 英式发音
            word['pron'] = box_simple.xpath(
                './/h2[@class="wordbook-js"]//div[@class="baav"]//*[@class="phonetic"]/text()').extract()[0]
            # 发音链接
            word['pron_url'] = "http://dict.youdao.com/dictvoice?audio=" + word['word'] + "&type=1"
            # 释义
            word['explain'] = []
            temp = box_simple.xpath('.//div[@class="trans-container"]//ul//li/text()').extract()
            for item in temp:
                if len(item) > 0 and not re.search(r'\n', item) and not re.match(r' ', item):
                    print(item)
                    word['explain'].append(item)
            # 例句
            time.sleep(3)
            word['example'] = []
            example_root = box.xpath('//*[@id="bilingual"]//ul[@class="ol"]/li')
            # 1.双语例句是否存在
            if example_root:
                for li in example_root:
                    en = ""
                    for span in li.xpath('./p[1]/span'):
                        if span.xpath('./text()').extract():
                            en += span.xpath('./text()').extract()[0]
                        elif span.xpath('./b/text()').extract():
                            en += span.xpath('./b/text()').extract()[0]
                    zh = str().join(li.xpath('./p[2]/span/text()').extract()).replace(' ', '')
                    word['example'].append(dict(en=en.replace('\"', '\\"'), zh=zh))
            #  2.柯林斯英汉双解大辞典的例句是否存在
            elif box.xpath('//*[@id="collinsResult"]//ul[@class="ol"]//div[@class="examples"]'):
                example_root = box.xpath('//*[@id="collinsResult"]//ul[@class="ol"]//li')
                for i in example_root:
                    if i.xpath('.//*[@class="exampleLists"]'):
                        en = i.xpath(
                            './/*[@class="exampleLists"][1]//div[@class="examples"]/p[1]/text()').extract()[0]
                        zh = i.xpath(
                            './/*[@class="exampleLists"][1]//div[@class="examples"]/p[2]/text()').extract()[0]
                        word['example'].append(dict(en=en.replace('\"', '\\"'), zh=zh))
                        if len(word['example']) >= 3:
                            break
            yield word

最后 yield word则是返回解析的word 给Item Pipeline,进行随后的数据过滤或者存储。

运行爬虫-爬取单词释义

运行爬虫,会爬取agree、prophet、proportion三个单词的详情,在项目目录下(scrapy.cfg所在的目录)

youdaoeng>scrapy crawl EngSpider -o data.json

即可运行,窗口可以看见爬取的日志内容输出,运行结束后会在项目目录下生成一个data.json文件。


开始爬取.jpg
Item输出.jpg
生成的data.json.jpg

生成的数据为所有item的json格式数组,中文字符都是Unicode编码,可通过一些在线的json解析网站如 https://www.bejson.com/ ,Unicode转中文查看是我们想要的结果。

下载单词语音文件

单词读音的mp3链接为解析时候保存的pron_url字段,接下来我们下载单词mp3文件到本地。
在Item下增加属性pron_save_path,存储发音文件的本地地址:

# 发音 mp3 本地存放路径
    pron_save_path = scrapy.Field()

并在settings.py文件中配置下载文件的目录,如在D:\scrapy_files\目录下,则配置

FILES_STORE = "D:\\scrapy_files\\"

增加ItemPipeline重新发起文件下载请求:

class Mp3Pipeline(FilesPipeline):
    '''
    自定义文件下载管道
    '''

    def get_media_requests(self, item, info):
        '''
        根据文件的url发送请求(url跟进)
        :param item:
        :param info:
        :return:
        '''
        # meta携带的数据可以在response获取到
        yield scrapy.Request(url=item['pron_url'], meta={'item': item})

    def item_completed(self, results, item, info):
        '''
        处理请求结果
        :param results:
        :param item:
        :param info:
        :return:
        '''
        file_paths = [x['path'] for ok, x in results if ok]
        if not file_paths:
            raise DropItem("Item contains no files")

        # old_name = FILES_STORE + file_paths[0]
        # new_name = FILES_STORE + item['word'] + '.mp3'

        # 文件重命名 (相当于剪切)
        # os.rename(old_name, new_name)

        # item['pron_save_path'] = new_name

        # 返回的result是除去FILES_STORE的目录
        item['pron_save_path'] = FILES_STORE + file_paths[0]
        return item

    def file_path(self, request, response=None, info=None):
        '''
        自定义文件保存路径
        默认的保存路径是在FILES_STORE下创建的一个full来存放,如果我们想要直接在FILES_STORE下存放,则需要自定义存放路径。
        默认下载的是无后缀的文件,需要增加.mp3后缀
        :param request:
        :param response:
        :param info:
        :return:
        '''
        file_name = request.meta['item']['word'] + ".mp3"
        return file_name

需要更改settings.py文件,配置Mp3Pipeline,后面的300为优先级,数字越大,优先级越低。

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    # 'youdaoeng.pipelines.YoudaoengPipeline': 300,
    'youdaoeng.pipelines.Mp3Pipeline': 300,
}

运行

youdaoeng>scrapy crawl EngSpider -o data1.json

等待运行完成,则在项目目录下生成了data1.json,并在D:\scrapy_files\目录下生成了我们爬取的三个单词的释义。

项目源码

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

推荐阅读更多精彩内容