scrapy 爬虫

2020-11-09 09:34:55 浏览数 (1)

Spider

#0 GitHub

代码语言:javascript复制
None

#1 环境

代码语言:javascript复制
Python3.6
Scrapy==1.6.0  # 安装Scrapy pip3 install Scrapy

#2 爬虫原理

#2.1 核心部件

  • Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
  • Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。
  • Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
  • Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器).
  • Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方。
  • Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
  • Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)

#2.2 Scrapy的运作流程

  • 1 引擎:Hi!Spider, 你要处理哪一个网站?
  • 2 Spider:老大要我处理xxxx.com。
  • 3 引擎:你把第一个需要处理的URL给我吧。
  • 4 Spider:给你,第一个URL是xxxxxxx.com。
  • 5 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  • 5 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  • 7 引擎:Hi!调度器,把你处理好的request请求给我。
  • 8 调度器:给你,这是我处理好的request
  • 9 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
  • 10 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
  • 11 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
  • 12 Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
  • 13 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
  • 14 管道调度器:好的,现在就做!

注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy也会重新下载。)

#3 制作 Scrapy 爬虫

  1. 新建项目(scrapy startproject xxx):新建一个新的爬虫项目
  2. 明确目标(编写items.py):明确你想要抓取的目标
  3. 制作爬虫(spiders/xxspider.py):制作爬虫开始爬取网页
  4. 存储内容(pipelines.py):设计管道存储爬取内容

#3.1 创建工程

代码语言:javascript复制
scrapy startproject mySpider # 新建爬虫项目
代码语言:javascript复制
.
├── mySpider
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
└── scrapy.cfg 
  • scrapy.cfg: 项目的配置文件。
  • mySpider/: 项目的Python模块,将会从这里引用代码。
  • mySpider/items.py: 项目的目标文件。
  • mySpider/pipelines.py: 项目的管道文件。
  • mySpider/settings.py: 项目的设置文件。
  • mySpider/spiders/: 存储爬虫代码目录。

#3.2 明确目标(mySpider/items.py)

我们打算抓取 http://www.dy100.me/ 网站里的电影名 / 电影详细信息url 。

  • 打开 mySpider 目录下的 items.py
  • Item 定义结构化数据字段,用来保存爬取到的数据,有点像 Python 中的 dict,但是提供了一些额外的保护减少错误。
  • 可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field 的类属性来定义一个 Item(可以理解为Django里面的models.py)。
代码语言:javascript复制
import scrapy

class MyspiderItem(scrapy.Item):
    name = scrapy.Field()  # 电影名
    info = scrapy.Field()  # 电影详细信息url

#3.3 制作爬虫 (spiders/itcastSpider.py)

爬虫功能要分两步

  1. 爬数据

在当前目录下输入命令,将在mySpider/spider目录下创建一个名为itcast的爬虫,并指定爬取域的范围:

代码语言:javascript复制
scrapy genspider itcast "itcast.cn" # 该命令会自动生成一个itcast.py文件,爬虫的主要逻辑代码就在里面写

打开 mySpider/spider目录里的 itcast.py,默认增加了下列代码:

代码语言:javascript复制
import scrapy

class ItcastSpider(scrapy.Spider):
    name = "itcast" # 唯一标识
    allowed_domains = ["itcast.cn"]
    start_urls = (
        'http://www.itcast.cn/', # 自动生成,需要手动修改成自己需要的url
    )

    def parse(self, response): # 爬虫爬下来的html页面
        pass 

其实也可以由我们自行创建itcast.py并编写上面的代码,只不过使用命令可以免去编写固定代码的麻烦

要建立一个Spider, 你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性 和 一个方法。

name = “” :这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。

allow_domains = [] 是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。

start_urls = () :爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。

parse(self, response) :解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下:

负责解析返回的网页数据(response.body),提取结构化数据(生成item) 生成需要下一页的URL请求。 将start_urls的值修改为需要爬取的第一个url

代码语言:javascript复制
start_urls = ("http://www.dy100.me/",) # 把url改成需要爬取的url

然后运行一下看看,在mySpider目录下执行:

代码语言:javascript复制
scrapy crawl itcast
  1. 取数据

XPath 表达式的例子及对应的含义:

代码语言:javascript复制
/html/head/title: 选择HTML文档中 <head> 标签内的 <title> 元素
/html/head/title/text(): 选择上面提到的 <title> 元素的文字
//td: 选择所有的 <td> 元素
//div[@class="mine"]: 选择所有具有 class="mine" 属性的 div 元素

举例我们读取网站 http://www.dy100.me/ 的网站标题,修改 itcast.py 文件代码如下::

代码语言:javascript复制
class ItcastSpider(scrapy.Spider):
    name = 'itcast'
    allowed_domains = ['itcast.cn']
    start_urls = [
        'http://www.dy100.me/',
        ]

    def parse(self, response):
        for foo in response.xpath('//*[@id="post_container"]/li/div[2]/h2/a/text()'):
            print("foo:",foo.extract()) # 尝试打印结果

#3.4 解析

只获取一个数据

代码语言:javascript复制
response.xpath('//*[@id="blogname"]/a/h1/text()').extract_first()

>>> 电影100|电影天堂
代码语言:javascript复制
# 测试数据
---
response.xpath('//*[@id="blogname"]/a/h1')
>>> [<Selector xpath='//*[@id="blogname"]/a/h1' data='<h1>电影100|电影天堂</h1>'>] # 拿到的是有个列表类型的数据,列表里的数据是Selector类型
---
response.xpath('//*[@id="blogname"]/a/h1').extract()
>>> ['<h1>电影100|电影天堂</h1>'] # 拿到的是一个列表的数据,每个数据是一个标签
---
response.xpath('//*[@id="blogname"]/a/h1').extract()[0]
>>><h1>电影100|电影天堂</h1> # 拿到是列表中第一个元素的数据
response.xpath('//*[@id="blogname"]/a/h1').extract_first() # 也是拿到列表中的第一个元素
---
response.xpath('//*[@id="blogname"]/a/h1/text()').extract_first()
>>> 电影100|电影天堂 # 拿到的是h1标签里面的内容 text()
---

如何获取标签属性的值

代码语言:javascript复制
response.xpath('//*[@id="blogname"]/a/img/@src').extract_first() # 获取src属性的值
response.xpath('//*[@id="blogname"]/a/img/@alt').extract_first() # 获取alt属性的值

如何获取所有电影的数据

代码语言:javascript复制
    def parse(self, response):

        for foo in response.xpath('//*[@id="post_container"]/li'): # 循环整个li标签, 拷贝出来的xpath是单个li标签,所以需要把li标签后边的数组下标去掉
            print("foo:",foo.xpath('div[2]/h2/a/text()').extract_first()) # 获取电影名, 标签前面不能有反斜杠
            print("foo:",foo.xpath('div[2]/h2/a/@href').extract_first()) # 获取详细信息url
        return None
代码语言:javascript复制
foo: 女孩 Girl (2018)
foo: http://www.dy100.me/15368.html
foo: 波西米亚狂想曲 Bohemian Rhapsody (2018)
foo: http://www.dy100.me/15365.html
foo: 乔纳斯 Jonas (2018)
foo: http://www.dy100.me/15362.html
foo: 谁先爱上他的 誰先愛上他的 (2018)
foo: http://www.dy100.me/15359.html
foo: 穷人·榴莲·麻药·偷渡客 窮人。榴槤。麻藥。偷渡客 (2012)
foo: http://www.dy100.me/15356.html
foo: 狼屋 La casa lobo (2018)
foo: http://www.dy100.me/15353.html
foo: 巴斯特·斯克鲁格斯的歌谣 The Ballad of Buster Scruggs (2018)
foo: http://www.dy100.me/15350.html
foo: 被抹去的男孩 Boy Erased (2018)
foo: http://www.dy100.me/15344.html
foo: 调琴师 Andhadhun (2018)
foo: http://www.dy100.me/15347.html
foo: 末代独裁 The Last King of Scotland (2006)
foo: http://www.dy100.me/15341.html
foo: 缄默的迷宫 Im Labyrinth des Schweigens (2014)
foo: http://www.dy100.me/15338.html
foo: 你还活着 Du levande (2007)
foo: http://www.dy100.me/15335.html
foo: 绿皮书 Green Book (2018)
foo: http://www.dy100.me/15332.html
foo: 90年代中期 Mid90s (2018)
foo: http://www.dy100.me/15329.html
foo: 无双 無雙 (2018)
foo: http://www.dy100.me/15326.html
foo: 花村 McCabe & Mrs. Miller (1971)
foo: http://www.dy100.me/15323.html
foo: 破浪 Breaking the Waves (1996)
foo: http://www.dy100.me/15320.html
foo: 安娜的旅程 Les Rendez-vous d'Anna (1978)
foo: http://www.dy100.me/15317.html

如何将数据输出

代码语言:javascript复制
scrapy crawl itcast # 不输出数据
scrapy crawl itcast -o info.json # 输出文件,文件名为info.json
代码语言:javascript复制
    def parse(self, response):
        items = []

        for foo in response.xpath('//*[@id="post_container"]/li'):
            name = foo.xpath('div[2]/h2/a/text()').extract_first()
            info = foo.xpath('div[2]/h2/a/@href').extract_first()
            item = MyspiderItem() # 实例化
            item["name"] = name # 赋值
            item["info"] = info # 赋值
            items.append(item) # 添加到列表中

        return items # 记得要返回

但是获取文件中保存的中文是Unicode编码

代码语言:javascript复制
[
{"name": "u5973u5b69 Girl (2018)", "info": "http://www.dy100.me/15368.html"},
{"name": "u6ce2u897fu7c73u4e9au72c2u60f3u66f2 Bohemian Rhapsody (2018)", "info": "http://www.dy100.me/15365.html"},
{"name": "u4e54u7eb3u65af Jonas (2018)", "info": "http://www.dy100.me/15362.html"},
{"name": "u8c01u5148u7231u4e0au4ed6u7684 u8ab0u5148u611bu4e0au4ed6u7684 (2018)", "info": "http://www.dy100.me/15359.html"},
{"name": "u7a77u4ebau00b7u69b4u83b2u00b7u9ebbu836fu00b7u5077u6e21u5ba2 u7aaeu4ebau3002u69b4u69e4u3002u9ebbu85e5u3002u5077u6e21u5ba2 (2012)", "info": "http://www.dy100.me/15356.html"},
{"name": "u72fcu5c4b La casa lobo (2018)", "info": "http://www.dy100.me/15353.html"},
{"name": "u5df4u65afu7279u00b7u65afu514bu9c81u683cu65afu7684u6b4cu8c23 The Ballad of Buster Scruggs (2018)", "info": "http://www.dy100.me/15350.html"},
{"name": "u88abu62b9u53bbu7684u7537u5b69 Boy Erased (2018)", "info": "http://www.dy100.me/15344.html"},
{"name": "u8c03u7434u5e08 Andhadhun (2018)", "info": "http://www.dy100.me/15347.html"},
{"name": "u672bu4ee3u72ecu88c1 The Last King of Scotland (2006)", "info": "http://www.dy100.me/15341.html"},
{"name": "u7f04u9ed8u7684u8ff7u5bab Im Labyrinth des Schweigens (2014)", "info": "http://www.dy100.me/15338.html"},
{"name": "u4f60u8fd8u6d3bu7740 Du levande (2007)", "info": "http://www.dy100.me/15335.html"},
{"name": "u7effu76aeu4e66 Green Book (2018)", "info": "http://www.dy100.me/15332.html"},
{"name": "90u5e74u4ee3u4e2du671f Mid90s (2018)", "info": "http://www.dy100.me/15329.html"},
{"name": "u65e0u53cc u7121u96d9 (2018)", "info": "http://www.dy100.me/15326.html"},
{"name": "u82b1u6751 McCabe & Mrs. Miller (1971)", "info": "http://www.dy100.me/15323.html"},
{"name": "u7834u6d6a Breaking the Waves (1996)", "info": "http://www.dy100.me/15320.html"},
{"name": "u5b89u5a1cu7684u65c5u7a0b Les Rendez-vous d'Anna (1978)", "info": "http://www.dy100.me/15317.html"}

修改settings.py

代码语言:javascript复制
FEED_EXPORT_ENCODING = 'utf-8' # 加上这行代码就可以在输出中文时不是Unicode编码
代码语言:javascript复制
{"name": "u88abu62b9u53bbu7684u7537u5b69 Boy Erased (2018)", "info": "http://www.dy100.me/15344.html"},
{"name": "u8c03u7434u5e08 Andhadhun (2018)", "info": "http://www.dy100.me/15347.html"},
{"name": "u672bu4ee3u72ecu88c1 The Last King of Scotland (2006)", "info": "http://www.dy100.me/15341.html"},
{"name": "u7f04u9ed8u7684u8ff7u5bab Im Labyrinth des Schweigens (2014)", "info": "http://www.dy100.me/15338.html"},
{"name": "u4f60u8fd8u6d3bu7740 Du levande (2007)", "info": "http://www.dy100.me/15335.html"},
{"name": "u7effu76aeu4e66 Green Book (2018)", "info": "http://www.dy100.me/15332.html"},
{"name": "90u5e74u4ee3u4e2du671f Mid90s (2018)", "info": "http://www.dy100.me/15329.html"},
{"name": "u65e0u53cc u7121u96d9 (2018)", "info": "http://www.dy100.me/15326.html"},
{"name": "u82b1u6751 McCabe & Mrs. Miller (1971)", "info": "http://www.dy100.me/15323.html"},
{"name": "u7834u6d6a Breaking the Waves (1996)", "info": "http://www.dy100.me/15320.html"},
{"name": "u5b89u5a1cu7684u65c5u7a0b Les Rendez-vous d'Anna (1978)", "info": "http://www.dy100.me/15317.html"}
]
[
{"name": "女孩 Girl (2018)", "info": "http://www.dy100.me/15368.html"},
{"name": "波西米亚狂想曲 Bohemian Rhapsody (2018)", "info": "http://www.dy100.me/15365.html"},
{"name": "乔纳斯 Jonas (2018)", "info": "http://www.dy100.me/15362.html"},
{"name": "谁先爱上他的 誰先愛上他的 (2018)", "info": "http://www.dy100.me/15359.html"},
{"name": "穷人·榴莲·麻药·偷渡客 窮人。榴槤。麻藥。偷渡客 (2012)", "info": "http://www.dy100.me/15356.html"},
{"name": "狼屋 La casa lobo (2018)", "info": "http://www.dy100.me/15353.html"},
{"name": "巴斯特·斯克鲁格斯的歌谣 The Ballad of Buster Scruggs (2018)", "info": "http://www.dy100.me/15350.html"},
{"name": "被抹去的男孩 Boy Erased (2018)", "info": "http://www.dy100.me/15344.html"},
{"name": "调琴师 Andhadhun (2018)", "info": "http://www.dy100.me/15347.html"},
{"name": "末代独裁 The Last King of Scotland (2006)", "info": "http://www.dy100.me/15341.html"},
{"name": "缄默的迷宫 Im Labyrinth des Schweigens (2014)", "info": "http://www.dy100.me/15338.html"},
{"name": "你还活着 Du levande (2007)", "info": "http://www.dy100.me/15335.html"},
{"name": "绿皮书 Green Book (2018)", "info": "http://www.dy100.me/15332.html"},
{"name": "90年代中期 Mid90s (2018)", "info": "http://www.dy100.me/15329.html"},
{"name": "无双 無雙 (2018)", "info": "http://www.dy100.me/15326.html"},
{"name": "花村 McCabe & Mrs. Miller (1971)", "info": "http://www.dy100.me/15323.html"},
{"name": "破浪 Breaking the Waves (1996)", "info": "http://www.dy100.me/15320.html"},
{"name": "安娜的旅程 Les Rendez-vous d'Anna (1978)", "info": "http://www.dy100.me/15317.html"}
]
~

如何解决输出的数据是否追加问题

如果使用命令scrapy crawl itcast -o info.json,那么每次执行,得到的新数据都会追加到info.json文件中

#4 保存数据

scrapy保存信息的最简单的方法主要有四种,-o 输出指定格式的文件,命令如下:

代码语言:javascript复制
scrapy crawl itcast -o teachers.json

json lines格式,默认为Unicode编码

代码语言:javascript复制
scrapy crawl itcast -o teachers.jsonl

csv 逗号表达式,可用Excel打开

代码语言:javascript复制
scrapy crawl itcast -o teachers.csv

xml格式

代码语言:javascript复制
scrapy crawl itcast -o teachers.xml

0 人点赞