2

前言

学习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.readthedoc...

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

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

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

{"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/... 。所以几乎可以认为单词的详情页链接可以是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/xp... 学习,需要熟悉语法、运算符、函数等。

    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目录下生成了我们爬取的三个单词的释义。

项目源码


所谓向日葵族
19 声望4 粉丝

Empty page presents more possibilities.