安装scrapy
框架
- 安装
scrapy
:pip install scrapy
- windows下,还需要安装
pypiwin32
,防止报错。pip install scrapy
。报Visual C++ 14.0 is required
错误的话,安装Visual C++ Build Tools
,Visual C++ Build Tools 2015下载地址,下载链接在网页的中间位置
创建项目和爬虫
- 创建项目:
scapy startproject [项目名称]
- 创建爬虫:命令行下,进入项目所在的路径,执行命令
scapy genspider [爬虫名字] [爬取的域名]
。注意:爬虫的名字不能和项目名字一样。
项目目录结构
items.py
:用来存放爬虫爬取下来数据的模型。middlewares.py
:用来存放各种中间件的文件。pipelines.py
:用来将items的模型存储到本地磁盘中。settings.py
:本爬虫的一些配置信息(比如请求头、多久发送一次请求、ip代理池等)。scrapy.cfg
:项目的配置文件。spiders包
:以后所有的爬虫,都是存放到这个里面。
修改settings.py
代码
在做一个爬虫之前,一定要记得修改setttings.py
中的设置。两个地方是强烈建议设置的。
ROBOTSTXT_OBEY
设置为False
。默认是True
。即遵守机器协议,那么在爬虫的时候,scrapy
首先去找robots.txt
文件,如果没有找到。则直接停止爬取。DEFAULT_REQUEST_HEADERS
添加User-Agent
。这个也是告诉服务器,我这个请求是一个正常的请求,不是一个爬虫。- 如果要激活
pipeline
,应该在settings.py
中,设置ITEM_PIPELINES
。 - 设置延时时间,
DOWNLOAD_DELAY = 1
运行scrapy项目
运行scrapy
项目。需要在终端,进入项目所在的路径,然后scrapy crawl [爬虫名字]
即可运行指定的爬虫。如果不想每次都在命令行中运行,那么可以把这个命令写在一个文件中。以后就在pycharm
中执行运行这个文件就可以了。比如现在新创建一个文件叫做start.py
,然后在这个文件中填入以下代码:
1 | from scrapy import cmdline |
糗事百科Scrapy爬虫笔记
response
是一个scrapy.http.response.html.HtmlResponse
对象。可以执行xpath
和css
语法提取数据。- 提取出来的数据,是一个
Selector
或者是一个SelectorList
对象。如果想要获取其中的字符串。使用getall
或者get
方法。 getall
方法:获取Selector
中的所有文本。返回的是一个列表。get
方法:获取的是Selector
中的第一个文本。返回的是一个str
类型。- 如果数据解析回来,要传给
pipeline
处理。那么可以使用yield
来返回。或者是收集所有的item
。最后统一使用return返回。 item
:建议在items.py
中定义好模型。以后就不要使用字典。pipeline
:这个是专门用来保存数据的。其中有三个方法是会经常用的。open_spider(self,spider)
:当爬虫被打开的时候执行process_item(self,item,spider
):当爬虫有item传过来的时候会被调用close_spider(self,spider)
:当爬虫关闭的时候会被调用
要激活pipeline
,应该在settings.py
中,设置ITEM_PIPELINES
。示例如下:
1 | ITEM_PIPELINES = { |
JsonItemExporter和JsonLinesItemExporter:
保存json
数据的时候,可以使用这两个类,让操作变得更简单。
JsonItemExport
每次把数据添加到内存中。最后统一写到磁盘中。
- 优点:存储的数据是一个满足json规则的数据
- 缺点:如果数据量比较大,比较耗内存
1 | from scrapy.exporters import JsonItemExporter |
JsonLinesItemExporter
每次调用export_item
的时候把这个item
存在到磁盘中
- 优点:每次处理数据的时候,直接存储到磁盘中,不耗内存。数据也比较安全。
- 缺点:每一个字典是一行,整个文件不是一个满足
json
格式的文件。
1 | from scrapy.exporters import JsonLinesItemExporter |
CrawlSpider
可以实现,只要满足某个条件的url
,都进行爬取。CrawlSpider
继承自Spider
,只不过是在之前的基础之上增加了新的功能,可以定义爬取的url
的规则,以后scrapy
碰到满足条件的url
都进行爬取,而不用手动的yield Request
。
创建CrawlSpider
爬虫
之前创建爬虫的方式是通过scrapy genspider [爬虫名字] [域名]
的方式创建的。如果想要创建CrawlSpider
爬虫,那么应该通过以下命令创建:
1 | scrapy genspider -t crawl [爬虫名字] [域名] |
LinkExtractors链接提取器
使用LinkExtractors
可以不用程序员自己提取想要的url
,然后发送请求。这些工作都可以交给LinkExtractors
,他会在所有爬的页面中找到满足规则的url
,实现自动的爬取。以下对LinkExtractors
类做一个简单的介绍:
1 | class scrapy.linkextractors.LinkExtractor( |
主要参数讲解:
allow
:允许的url。所有满足这个正则表达式的url都会被提取。deny
:禁止的url。所有满足这个正则表达式的url都不会被提取。allow_domains
:允许的域名。只有在这个里面指定的域名的url才会被提取。deny_domains
:禁止的域名。所有在这个里面指定的域名的url都不会被提取。restrict_xpaths
:严格的xpath。和allow共同过滤链接。
Rule规则类
1 | class scrapy.spiders.Rule( |
主要参数讲解:
link_extractor
:一个LinkExtractor
对象,用于定义爬取规则。callback
:满足这个规则的url,应该要执行哪个回调函数。因为CrawlSpider
使用了parse
作为回调函数,因此不要覆盖parse
作为回调函数自己的回调函数。follow
:指定根据该规则从response中提取的链接是否需要跟进。process_links
:从link_extractor
中获取到链接后会传递给这个函数,用来过滤不需要爬取的链接。
使用说明
LinkExtractor
和Rule
决定爬虫的具体走向。
allow
设置规则的方法:能够限制在我们想要的url上,不与其它的url相同的正则表达式即可。follow
使用场景:如果在爬取页面的时候,需要将满足当前条件的url再进行跟进,那么就设置为True
。否则设置为False
。callback
使用场景:如果这个url对应的页面,只是为了获取更多的url,并不需要里面的数据,可以不指定callback。如果想获取url对应页面中的数据,那么久需要制定一个callback。
1 | from scrapy.linkextractors import LinkExtractor |
Scrapy Shell
我们想要在爬虫中使用xpath
、beautifulsoup
、正则表达式
、css选择器
等来提取想要的数据。但是因为scrapy
是一个比较重的框架。每次运行起来都要等待一段时间。因此要去验证我们写的提取规则是否正确,是一个比较麻烦的事情。因此scrapy
提供了一个shell
,用来方便的测试规则。当然也不仅仅局限于这一个功能。
打开Scrapy Shell
- 打开
cmd
终端,进入到scrapy
框架所在的虚拟环境中,输入命令scrapy shell [链接]
。就会进入到scrapy
的shell
环境中。在这个环境中,你可以跟在爬虫的parse
方法中一样使用了 - 如果想读取某个项目的配置信息,应先进入到这个项目目录,再执行
scrapy shell
命令
Request和Response对象
Request对象
Request
对象在我们写爬虫,爬取一页的数据需要重新发送一个请求的时候调用。这个类需要传递一些参数,其中比较常用的参数有:
url
:这个request
对象发送请求的url
。callback
:在下载器下载完成相应的数据后执行的回调函数method
:请求的方法。默认为GET
方法,可以设置为其它方法。headers
:请求头,对于一些固定的设置,放在settings.py
中指定就可以了。对于那些非固定的,可以在发送请求的时候指定。meta
:比较常用。用于在不同的请求之间传递数据用的encoding
:编码。默认为utf-8
,使用默认的即可。dot_filter
:表示不由调度器过滤。在执行多次重复的请求时用得比较多。errback
:在发生错误时执行的函数。
Response
Response
对象一般是由Scrapy
自动构建的。因此开发者不需要关心如何创建Response
对象,而是要了解如何使用。Response
对象有很多属性,可以用来提取数据:
meta
:从其它请求传过来的meta
属性,可以用来保持多个请求之间的数据连接。encoding
:返回当前字符串编码和解码的格式text
:将返回来的数据作为Unicode
字符串返回body
:将返回的数据作为bytes
字符串返回xpath
:xpath
选择器css
:css
选择器
发送POST请求
有时候想要在请求数据的时候发送post
请求,那么这时候需要使用Request
的子类FormRequest
来实现。如果想要在爬虫一开始的时候就发送POST
请求,需要在爬虫类中重写start_request(self)
方法,并且不再调用start_urls
里的url
。
下载文件和图片
Scrapy
为下载item
中包含的文件(比如图片)提供了一个可重用的item pipelines
。这些pipeline
有些共同的方法和结构(我们称之为media pipeline
)。一般来说会使用Files Pipeline
或Images Pipeline
。
scrapy内置下载文件方法的优点
- 避免重新下载最近已经下载过的数据
- 可以方便的指定文件存储的路径
- 可以将下载的图片转换成通用的格式。比如
png
或jpg
。 - 可以方便的生成缩略图
- 可以方便的检测图片的宽和高,确保他们满足最小限制
- 异步下载,效率非常高
下载文件的Files Pipline
当使用Files Pipline
下载文件的时候,按照以下步骤来完成:
- 定义好一个
Item
,然后在这个Item
中定义两个属性,分别为file_urls
以及files
。file_urls
是用来存储需要下载的图片的url
链接,需要给一个列表 - 当文件下载完成后,会把文件下载的相关信息存储到
item
的files
属性中。比如下载路径、下载的url和文件的校验码等 - 在配置文件
settings.py
中配置FILES_STORE
,这个配置是用来设置文件下载下来的路径 - 启动
pipeline
:在ITEM_PIPELINES
中设置scrapy.pipeline.files.FilesPipeline: 1
下载图片的Images Pipeline
当使用Images Pipeline
下载文件的时候,按照以下步骤来完成:
- 定义好一个
Item
,然后再这个item
中定义两个属性,分别为image_urls
以及images
。image_urls
是用来存储需要下载的图片的url
链接,需要给一个列表 - 当文件下载完成后,会把文件下载的相关细信息存储到
item
的images
属性中。比如下载路径、下载的url
和图片的校验码等。 - 在配置文件
settings.py
中配置IMAGES_STORE
,这个配置是用来设置图片下载下来的路径。 - 启动
pipeline
:在ITEM_PIPELINES
中设置scrapy.pipelines.images.ImagesPipeline: 1
Downloader Middlewares(下载器中间件)
下载器中间件是引擎和下载器之间通信的中间件。在这个中间件中可以设置代理、更换请求头等来达到反反爬虫的目的。要写下载器中间件,可以在下载器中实现两个方法。一个是process_request(self, request, spider)
,这个方法是在请求发送之前会执行,还有一个是process_response(self, request, response, spider)
,这个方法是数据下载到引擎之前执行。
process_request(self, request, spider):
这个方法是下载器在发送请求之前会执行的。一般可以在这个里面设置随机代理ip等。
- 参数:
request
:发送请求的request对象spider
:发送请求的spider对象
- 返回值:
- 返回
None
:如果返回None
,Scrapy
将继续处理该request
,执行其它中间件中的相应方法,知道合适的下载器处理函数被调用, - 返回
Response
对象:Scrapy
将不会调用任何其它的process_request
方法,将直接返回这个response
对象。已经激活的中间件的process_response()
方法则会在每个response
返回时调用。(有疑问) - 返回
Request
对象:不在使用之前的request
对象去下载数据,而是根据现在返回的request
对象返回数据。 - 如果这个方法中抛出了异常,则会调用
process_exception
方法
process_response(self, request, response, spider)
这个是下载器下载的数据到引擎中间会执行的方法
- 参数:
- request:request对象
- response:被处理的response对象
- spider:spider对象
- 返回值:
- 返回
Response
对象:会将这个新的response
对象传给其它中间件,最终传给爬虫 - 返回
Request
对象:下载器链被切断,返回的request
会重新被下载器调度下载 - 如果抛出一个异常,那么调用
request
的errback
方法,如果没有指定这个方法,那么会抛出一个异常。
随机请求头中间件
爬虫在频繁访问一个页面的时候,这个请求头如果一直保持一直。那么很容易被服务器发现,从而禁止这个请求头的访问。因此我们要在访问这个页面之前随机的更改请求头,这样才可以避免爬虫被抓。随机更改请求头,可以在下载器中间件中实现。在请求发送给服务器之前,随机的选择一个请求头。示例代码如下:
1 | import random |
ip代理池中间件
购买代理
芝麻地理,太阳代理等
使用ip代理池
1 | # 中间件 |
使用独享代理的话:
1 | # 中间件 |
redis
redis
是一种支持分布式的nosql
数据库,他的数据是保存在内存中,同时redis
可以定时把内存数据同步到磁盘,即可以将数据持久化,并且他比memcached
支持更多的数据结构(string
,list列表[队列和栈]
,set[集合]
,sorted set[有序集合]
,hash(hash表)
)。相关参考文档:Redis 命令参考
redis使用场景:
- 登录会话存储:存储在
redis
中,与memcached
相比,数据不会丢失。 - 排行版/计数器:比如一些秀场类的项目,经常会有一些前多少名的主播排名。还有一些文章阅读量的技术,或者新浪微博的点赞数等。
- 作为消息队列:比如
celery
就是使用redis
作为中间人。 - 当前在线人数:还是之前的秀场例子,会显示当前系统有多少在线人数。
- 一些常用的数据缓存:比如我们的BBS论坛,板块不会经常变化的,但是每次访问首页都要从
mysql
中获取,可以在redis
中缓存起来,不用每次请求数据库。 - 把前200篇文章缓存或者评论缓存:一般用户浏览网站,只会浏览前面一部分文章或者评论,那么可以把前面200篇文章和对应的评论缓存起来。用户访问超过的,就访问数据库,并且以后文章超过200篇,则把之前的文章删除。
- 好友关系:微博的好友关系使用redis实现。
- 发布和订阅功能:可以用来做聊天软件。
redis和memcached的比较
memecached | redis | |
---|---|---|
类型 | 纯内存数据库 | 内存磁盘同步数据库 |
数据类型 | 在定义value时就要固定数据类型 | 不需要 |
虚拟内存 | 不支持 | 支持 |
过期策略 | 支持 | 支持 |
存储数据安全 | 不支持 | 可以将数据同步到dump.db中 |
灾难恢复 | 不支持 | 可以将磁盘中的数据恢复到内存中 |
分布式 | 支持 | 主从同步 |
订阅与发布 | 不支持 | 支持 |
其它机器访问本机redis服务器
想要让其他机器访问本机的redis
服务器。那么要修改redis.conf
的配置文件,将bind
改成bind [自己的ip地址或者0.0.0.0]
,其他机器才能访问。
注意:bind
绑定的是本机网卡的ip地址,而不是想让其他机器连接的ip地址。如果有多块网卡,那么可以绑定多个网卡的ip地址。如果绑定到额是0.0.0.0
,那么意味着其他机器可以通过本机所有的ip地址进行访问。
对redis的操作
对redis
的操作可以用两种方式,第一种方式采用redis-cli
,第二种方式采用编程语言,比如Python
、PHP
和JAVA
等。下面使用redis-cli
对redis
进行操作。
字符串操作:
启动redis:
1 | sudo service redis-server start |
连接上redis-server:
1 | redis-cli -h [ip] -p [端口] |
添加:set key value
。将字符串值value
关联到key
。如果key
已经持有其他值,set
命令就覆写旧值,无视其类型。并且默认的过期时间是永久,即永远不会过期。
1 | set username xiaotuo |
删除:del key
1 | del username |
设置过期时间:
1 | expire key timeout(单位为秒) |
也可以在设置值的时候,一同指定过期时间:
1 | set key value EX timeout |
查看过期时间:ttl key
1 | ttl username |
查看当前redis
中的所有key
:
1 | keys * |
列表操作
在列表左边添加元素:将值value
插入到列表key
的表头。如果key
不存在,一个空列表会被创建并执行lpush
操作。当key
存在但不是列表类型时,将返回一个错误。
1 | lpush key value |
在列表右边添加元素:将值value
插入到列表key
的表尾。如果key
不存在,一个空列表会被创建并执行rpush
操作。当key
存在但不是列表类型时,返回一个错误。
1 | rpush key value |
查看列表中的元素:返回列表key
中指定区间内的元素,区间以偏移量start
和stop
指定,如果要左边的第一个到最后的一个lrange key 0 -1
。
1 | lrange key start stop |
移除列表中的元素:
移除并返回列表key的头元素:
1 | lpop key |
移除并返回列表的尾元素:
1 | rpop key |
移除并返回列表key
的中间元素:将删除key这个列表中,值为value的元素,count为指定个数
1 | lrem key count value |
指定返回第几个元素:将返回key
这个列表中,索引为index
的这个元素。
1 | lindex key index |
获取列表中的元素个数:llen key
1 | llen languages |
删除指定的元素:lrem key count value
1 | lrem languages 0 php |
根据参数count
的值,移除列表中与参数value
相等的元素。count
的值可以是以下几种:
- count > 0:从表头开始向表尾搜索,移除与
value
相等的元素,数量为count
。 - count < 0:从表尾开始向表头搜索,移除与
value
相等的元素,数量为count
的绝对值。 - count = 0:移除表中所有与
value
相等的值。
set集合的操作
添加元素:sadd set value1 value2....
1 | sadd team xiaotuo datuo |
查看元素:smembers set
1 | smembers team |
移除元素:srem set member...
1 | srem team xiaotuo datuo |
查看集合中的元素个数:scard set
1 | scard team1 |
获取多个集合的交集:sinter set1 set2
1 | sinter team1 team2 |
获取多个集合的并集:sunion set1 set2
1 | sunion team1 team2 |
获取多个集合的差集:sdiff set1 set2
1 | sdiff team1 team2 |
hash哈希操作
添加一个新值:hset key field value
。将哈希表key
中的域field
的值设为value
。如果key
不存在,一个新的哈希表被创建并进行hset
操作。如果域field
已经存在于哈希表中,旧值将被覆盖。
1 | hset website baidu baidu.com |
获取哈希中的field
对应的值:hget key field
1 | hget website baidu |
删除field
中的某个field
:hdel key field
1 | hdel website baidu |
获取某个哈希中所有的field
和value
:hgetall key
1 | hgetall website |
获取某个哈希中所有的field
:hkeys key
1 | hkeys website |
获取某个哈希中所有的值:hvals key
1 | hvals website |
判断哈希中是否存在某个field
:hexists key field
1 | hexists website baidu |
获取哈希中键值对的数量:hlen field
1 | hlen website |
其它知识点用到再补充
Scrapy-Redis分布式爬虫组件
Scrapy
是一个框架,他本身是不支持分布式的。如果我们想要做分布式的爬虫,就需要借助一个组件叫做Scrapy-Redis
,这个组件正是利用了Redis
可以分布式的功能,集成到Scrapy
框架中,使得爬虫可以进行分布式。可以充分的利用资源(多个ip、更多带宽、同步爬取)来提高爬虫的爬行效率。
分布式爬虫的优点
- 可以充分利用多台机器的带宽。
- 可以充分利用多台机器的ip地址。
- 多台机器做,爬取效率更高。
分布式爬虫必须要解决的问题
- 分布式爬虫是好几台机器在同时运行,如何保证不同的机器爬取页面的时候不会出现重复爬取的问题。
- 同样,分布式爬虫在不同的机器上运行,在把数据爬完后如何保证保存在同一个地方。
安装
通过pip install scrapy-redis
即可安装。
Scrapy-Redis架构
这块少图,有空了找个高清的。
Item Pipeline
在接收到数据后发送给了Redis
、Scheduler
调度器调度数据也是从Redis
中来的、并且其实数据去重也是在Redis
中做的。
编写Scrapy-Redis分布式爬虫
要将一个Scrapy
项目变成一个Scrapy-redis
项目只需修改以下三点就可以了:
- 将爬虫的类从
scrapy.Spider
变成scrapy_redis.spiders.RedisSpider
;或者是从scrapy.CrawlSpider
变成scrapy_redis.spiders.RedisCrawlSpider
。 - 将爬虫中的
start_urls
删掉。增加一个redis_key="xxx"
。这个redis_key
是为了以后在redis
中控制爬虫启动的。爬虫的第一个url
,就是在redis
中通过这个发送出去的。 - 在配置文件中增加如下配置:
1 | # Scrapy-Redis相关配置 |
运行爬虫
- 在爬虫服务器上。进入爬虫文件所在的路径,然后输入命令:
scrapy runspider [爬虫名字]
。 - 在
Redis
服务器上,推入一个开始的url
链接:redis-cli> lpush [redis_key] start_url
开始爬取。