该文章涉及方面比较多,后面会将该文章拆开,每个方面都会进行详细的说明和使用,但是该文件的内容不变。
Scrapy 爬虫框架的使用 手册
基础介绍
安装
pip install Twisted-18.9.0-cp36-cp36m-win_amd64.whl
pip install Scrapy
错误
ModuleNotFoundError: No module named ‘win32api’
pip install pypiwin32
创建项目
scrapy startproject myscrapy
目录介绍
scrapy.cfg
: 项目的配置文件myscrapy
:项目myscrapy/items.py
:项目使用的item文件myscrapy/pipelines.py
: 项目中的pipelines文件.myscrapy/settings.py
: 项目的设置文件.myscrapy/spiders/
: 放置spider代码的目录.
创建一个爬虫的应用
# 创建一个名称叫danke 网址后缀为danke.com的爬虫应用
scrapy genspider danke danke.com
# 此时在myscrapy/spiders目录下面有了一个danke.py的文件 打开
修改items.py
import scrapy
class CrawlsHouseItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
class DanKe(CrawlsHouseItem):
# 房子名称
host_name = scrapy.Field()
# 租金
price = scrapy.Field()
# room list
room_list = scrapy.Field()
修改danke.py
# -*- coding: utf-8 -*-
import scrapy
from crawls_house.items import DanKe
class DankeSpider(scrapy.Spider):
# 应用的名称
name = 'danke'
# 只有是该域名的连接才会被调度
allowed_domains = ['www.danke.com']
# 起始页面的url
start_urls = ['https://www.danke.com/room/bj']
def parse(self, response):
# 获取需要继续跟进的url
for href in response.xpath("//div[@class='r_lbx_cena']//a//@href").extract():
# 将需要跟进的url交给调度器处理并指定回调方法为parse_item
yield scrapy.Request(href, self.parse_item)
# 获取下一页的url 自动会进行去重
for next_href in response.xpath("//div[@class='page']//a//@href").extract():
yield scrapy.Request(next_href)
def parse_item(self, response):
# 处理详情页面的信息
danke = DanKe()
# 房子的名称
danke['host_name'] = response.xpath('//h1//text()').extract()[0]
# 租金
price = response.xpath('//div[@class="room-price-sale"]//text()').extract()[0]
danke['price'] = price.replace(" ", "").replace("\n", "")
# room list
room_list = []
for room in response.xpath('//div[@class="room-list"]//text()').extract():
room = room.replace(" ", "").replace("\n", "")
if len(room) != 0:
room_list.append(room)
danke["room_list"] = ",".join(room_list)
# 返回 Item 交给 pipline 处理
return danke
修改 settings.py
# 设置 User-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
# 是否遵循 robots.txt 协议
ROBOTSTXT_OBEY = False
# 是否关闭 cookies (默认启用)
COOKIES_ENABLED = False
# 使用 request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
#item piplines
ITEM_PIPELINES = {
'crawls_house.pipelines.CrawlsHousePipeline': 300,
}
修改pipelines.py
class CrawlsHousePipeline(object):
def __init__(self):
super().__init__()
self.file = open("danke.json", "a", encoding="utf-8")
def __del__(self):
self.file.close()
def process_item(self, item, spider):
print(item, spider)
self.file.write("{}|{}|{}\n".format(item["host_name"], item["price"], item["room_list"]))
return item
启动项目
scrapy crawl danke
命令行使用
创建项目
scrapy startproject myproject
创建一个spider
# scrapy genspider [-t template] <name> <domain>
scrapy genspider myspider myspider.com
# 查看可以使用的template
scrapy genspider -l
# 查看template 的内容
scrapy genspider -d basic
运行项目
scrapy crawl myspider
检查spider
scrapy check -l
scrapy check
列出当前可用的spider
scrapy list
查看页面返回结果
scrapy fetch --nolog --headers https://www.danke.com/room/bj
用浏览器打开页面
scrapy view https://www.danke.com/room/bj
命令行执行scrapy
scrapy shell https://www.danke.com/room/bj
对url进行分析
scrapy parse <url> [options]
-
--spider=SPIDER
: 跳过自动检测spider并强制使用特定的spider -
--a NAME=VALUE
: 设置spider的参数(可能被重复) -
--callback
or-c
: spider中用于解析返回(response)的回调函数 -
--pipelines
: 在pipeline中处理item -
--rules
or-r
: 使用CrawlSpider
规则来发现用来解析返回(response)的回调函数 -
--noitems
: 不显示爬取到的item -
--nolinks
: 不显示提取到的链接 -
--nocolour
: 避免使用pygments对输出着色 -
--depth
or-d
: 指定跟进链接请求的层次数(默认: 1) -
--verbose
or-v
: 显示每个请求的详细信息
Item Pipline
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。
每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。
以下是item pipeline的一些典型应用:
- 清理HTML数据
- 验证爬取的数据(检查item包含某些字段)
- 查重(并丢弃)
- 将爬取结果保存到数据库中
编写自己的item pipline
- 重写以下方法
import json
class MyItemPipline(object):
def __init__(self, file_name):
self.filename = file_name
def open_spider(self, spider):
# spider 开始时被调用
sefl.file = open(self.filename, "a", encoding="utf-8")
pass
def close_spider(self, spider):
# spider 结束时被调用
self.file.close()
pass
def process_item(self, item, spider)
# 每个item pipeline组件都需要调用该方法,这个方法必须返回一个 Item (或任何继承类)对象, 或是抛出 DropItem 异常,被丢弃的item将不会被之后的pipeline组件所处理。
if item['id'] == 1:
raise DropItem("item is get")
else:
self.file.write("%s\n" % json.dumps(dict(item)))
return item
@classmethod
def from_crawler(cls, crawler):
# 从crawler的配置文件中获取配置信息 需要设置其为类方法
return cls(
file_name = crawler.settings.get("file_name")
)
-
在配置文件最后那个添加该组件
分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。
ITEM_PIPELINES = { 'crawls_house.pipelines.CrawlsHousePipeline': 300, 'crawls_house.pipelines.CrawlsHousePipeline2': 400, }
Logging
-
使用日志
from scrapy import log log.msg("logging warning", level=log.WARNING)
-
将日志导出到文件
scrapy crawl danke --logfile=danke.log
Telent
使用telent终端访问scrapy
# 默认监听本地的6023端口
telnet localhost 6023
快捷名称 | 描述 |
---|---|
crawler() | Scrapy Crawler (scrapy.crawler.Crawler 对象) |
engine() | Crawler.engine属性 |
spider() | 当前激活的爬虫(spider) |
slot() | the engine slot |
extensions() | 扩展管理器(manager) (Crawler.extensions属性) |
stats() | 状态收集器 (Crawler.stats属性) |
settings() | Scrapy设置(setting)对象 (Crawler.settings属性) |
est() | 打印引擎状态的报告 |
prefs() | 针对内存调试 (参考 调试内存溢出) |
p() | pprint.pprint 函数的简写 |
hpy() | 针对内存调试 |
# 暂停爬虫
telnet localhost 6023
>>> engine.pause()
# 恢复爬虫
>>> engine.unpause()
# 停止爬虫
>>> engine.stop()
Setting 配置
# 设置 telnet 的端口
TELNETCONSOLE_PORT = [6023, 6073]
# 监听的地址
TELNETCONSOLE_HOST = '127.0.0.1'
下载中间件
动态随机User-Agent和使用代理
-
在settings.py设置
DOWNLOADER_MIDDLEWARES = { 'crawls_house.middlewares.CrawlsHouseDownloaderMiddleware': 543, }
-
在middlewares.py的CrawlsHouseDownloaderMiddleware类中修改
其他的方法不要改变
import random from crawls_house.settings import USER_AGENTS, PROXIES class CrawlsHouseDownloaderMiddleware(object): def process_response(self, request, response, spider): # 设置 user-agent request.headers["User-Agent"] = random.choice(USER_AGENTS) # 设置 proxy request.meta["proxy"] = "http://%s" % random.choice(PROXIES) return response
Spider中间件
图片下载
pip install Pillow
-
在item.py文件的需要进行图片下载的类中添加属性
image_urls = scrapy.Field() image_paths = scrapy.Field()
-
在settings.py文件中添加
ITEM_PIPELINES = { 'crawls_house.pipelines.XMLExportItem': 300, # 这是自定义的 "crawls_house.pipelines.MyImagesPipeline": 299, # 这是基本的 不可和自定义的同时设置 "scrapy.contrib.pipeline.images.ImagesPipeline", 299 } # 图片的下载地址 IMAGES_STORE = r'D:\workspace\crawls_house\imgs' # 图片的有效时间 IMAGES_EXPIRES = 90 # 图片缩略图生成 IMAGES_THUMBS = { 'small': (50, 50), 'big': (270, 270), }
-
如果使用了自定义 则需要在pipeline.py文件中添加
from scrapy.contrib.pipeline.images import ImagesPipeline from scrapy.exceptions import DropItem class MyImagesPipeline(ImagesPipeline): def item_completed(self, results, item, info): image_paths = [x['path'] for ok, x in results if ok] if not image_paths: raise DropItem("Item contains no images") item['image_paths'] = image_paths return item
自动限速
# 是否启用限速扩展
AUTOTHROTTLE_ENABLED = True
# 初始限速速度(单位秒)。
AUTOTHROTTLE_START_DELAY = 5.0
# 在高延迟情况下最大的下载延迟(单位秒)。
AUTOTHROTTLE_MAX_DELAY = 60.0
# 起用AutoThrottle调试(debug)模式,展示每个接收到的response。 您可以通过此来查看限速参数是如何实时被调整的。
AUTOTHROTTLE_DEBUG = True
Jobs 暂停和恢复爬虫
-
在 settings.py文件中指定
# 爬虫的job恢复路径 JOBDIR = "crawl_job/crawls-1"
-
在命令行指定
scrapy crawl danke -s JOBDIR=crawls_job/danke-1
同一时间启动多个Spider
-
在crawls_house下创建crawlall.py文件
from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings class Command(ScrapyCommand): requires_project = True def syntax(self): return '[options]' def short_desc(self): return 'Runs all of the spiders' def run(self, args, opts): spider_list = self.crawler_process.spiders.list() for name in spider_list: self.crawler_process.crawl(name, **opts.__dict__) self.crawler_process.start()
-
在 settings.py文件中添加
COMMANDS_MODULE = 'crawls_house'
-
启动
scrapy crawlall
分布式爬虫
-
安装scrapy-redis
pip install scrapy-redis
-
修改settings.py文件
# 启用Redis调度存储请求队列. SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 确保所有的爬虫通过Redis去重 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # Default requests serializer is pickle, but it can be changed to any module # with loads and dumps functions. Note that pickle is not compatible between # python versions. # Caveat: In python 3.x, the serializer must return strings keys and support # bytes as values. Because of this reason the json or msgpack module will not # work by default. In python 2.x there is no such issue and you can use # 'json' or 'msgpack' as serializers. # 默认请求序列化使用的是pickle 但是我们可以更改为其他类似的。PS:这玩意儿2.X的可以用。3.X的不能用 # SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat" # 不清除Redis队列、这样可以暂停/恢复 爬取 # SCHEDULER_PERSIST = True # 使用优先级调度请求队列 (默认使用) # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue' # 可选用的其它队列 # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue' # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' # 最大空闲时间防止分布式爬虫因为等待而关闭 # 这只有当上面设置的队列类是SpiderQueue或SpiderStack时才有效 # 并且当您的蜘蛛首次启动时,也可能会阻止同一时间启动(由于队列为空) # SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 将清除的项目在redis进行处理 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 1, 'crawls_house.pipelines.XMLExportItem': 300, "crawls_house.pipelines.MyImagesPipeline": 299 } # 序列化项目管道作为redis Key存储 # REDIS_ITEMS_KEY = '%(spider)s:items' # 默认使用ScrapyJSONEncoder进行项目序列化 # REDIS_ITEMS_SERIALIZER = 'json.dumps' # 指定连接到redis时使用的端口和地址(可选) REDIS_HOST = '192.168.1.203' REDIS_PORT = 60790 # 指定用于连接redis的URL(可选) # 如果设置此项,则此项优先级高于设置的REDIS_HOST 和 REDIS_PORT # REDIS_URL = 'redis://user:pass@hostname:9001' # 自定义的redis参数(连接超时之类的) # REDIS_PARAMS = {} # 自定义redis客户端类 # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 如果为True,则使用redis的'spop'进行操作。 # #如果需要避免起始网址列表出现重复,这个选项非常有用。开启此选项urls必须通过sadd添加,否则会出现类型错误。 # REDIS_START_URLS_AS_SET = False # RedisSpider和RedisCrawlSpider默认 start_usls 键 #REDIS_START_URLS_KEY = '%(name)s:start_urls' # 设置redis使用utf-8之外的编码 # REDIS_ENCODING = 'latin1'
-
修改***tongcheng.py***文件
只修改必要部分,其余部分不变# 引入 redisSpider类 from scrapy_redis.spiders import RedisSpider # 继承该类 class TongchengSpider(RedisSpider): name = 'tongcheng' allowed_domains = ['58.com'] # redis中是的key redis_key = "tongcheng:start_urls"
-
启动爬虫,此时爬虫并没有开始爬取
scrapy runspider spider/tongcheng.py
-
向redis中添加键,爬虫开始运行
lpush tongcheng:start_urls https://bj.58.com/chuzu/?PGTID=0d3090a7-0000-126a-c26e-b315d60fe251&ClickID=1
架构概览
组件
Scrapy Engine
引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。 详细内容查看下面的数据流(Data Flow)部分。
调度器(Scheduler)
调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎。
下载器(Downloader)
下载器负责获取页面数据并提供给引擎,而后提供给spider。
Spiders
Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。 每个spider负责处理一个特定(或一些)网站。 更多内容请看 Spiders 。
Item Pipeline
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)。 更多内容查看 Item Pipeline 。
下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。更多内容请看 下载器中间件(Downloader Middleware) 。
Spider中间件(Spider middlewares)
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。更多内容请看 Spider中间件(Middleware) 。
数据流(Data flow)
Scrapy中的数据流由执行引擎控制,其过程如下:
- 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
- 引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
- 引擎向调度器请求下一个要爬取的URL。
- 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
- 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
- 引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
- Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
- 引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
- (从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。
事件驱动网络(Event-driven networking)
Scrapy基于事件驱动网络框架 Twisted 编写。因此,Scrapy基于并发性考虑由非阻塞(即异步)的实现。
关于异步编程及Twisted更多的内容请查看下列链接: