- Engine: 引擎,处理整个系统的数据流处理、触发事务,是整个框架的核心。
- item: 项目,定义爬取结果的数据结构,爬取的数据会被复制成该 item 对象。
- Scheduler: 调度器,接收引擎发过来的请求,并将其加入队列中,在引擎再次请求的时候将请求提供给引擎。
- Downloader: 下载器,下载网页内容,并将网页内容返回给蜘蛛。
- Spiders: 蜘蛛,其内定义了爬取的逻辑和网页的解析规则,它主要负责解析响应并生成提取结果和新的请求。
- Item Pipline: 项目管道,负责处理有蜘蛛从网页中抽取的项目,它的主要任务是清晰、验证和存储数据。
- Downloader Middlewares: 下载器中间件,位于引擎和下载器之间的钩子框架,主要处理引擎与下载器之间的请求及响应。
- Spider Middlewares: 蜘蛛中间件,位于引擎和蜘蛛之间的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求。
抓取流程:
- Engine 打开一个网站,找到处理该网站的 Spider , 并向该 Spider请求第一个要爬取的 url 。
- Engine 从 Spider 中获取到第一个要爬取的 Url ,并通过 Scheduler 以 request 的形式调度。
- Engine 向 Scheduler 请求下一个要爬取的Url。
- Scheduler 返回下一个要爬取的Url 给 Engine, Engine 将 Url 通过 Downloader Middlewares 转发给Downloader 下载。
- 页面下载完毕后,Downloader 生成该页面的response , 并将其通过 Downloader Middlewares 转发给Engine。
- Engine 从下载器中接收到response ,并将其通过Spider Middlewares 发送给Spider 处理。
- Engine 处理 response , 返回爬取到 的Item 及新的Request 给 Engine。
- Engine 将 Spider 返回的Item 给Item Pipeline, 将新的 Request 给 Scheduler。
- 重复 第 2 步 到第 8 步,直到 Schrduler 中没有 更多的 request , Engine 关闭该网站,爬取结束。
我的理解
scrapy 创建命令
scrapy 查看可用命令
scrapy startproject 项目名称 创建项目
scrapy genspider 蜘蛛名称 需要抓取的网站域名 创建起始spider
scrapy shell 网站链接 在抓取时可用 shell 命令进行测试
备注:创建完成项目后需要使用 cd 命令进入项目目录,然后再创建 spider
scrapy 项目文件结构:
- scrapy.cfg: Scrapy 项目的配置文件,其内定义了项目的配置文件路径、部署相关信息等内容。
- items.py: 定义 Item 数据结构,所有的Item的定义都可以放在这里。
- pipelines.py: 定义 Item Pipeline 的实现。
- setting.py: 定义项目的全局配置。
- middlewares.py: 定义 Spider Middlewares 和 Downloader Middlewares 的实现。
- spiders: 其内包含一个个的 Spider 的实现。
scrapy 创建一个项目流程:
windows + R # 打开运行框 输入 cmd 回车 打开小黑框
scrapy startproject tutorial # 生成 scrapy 项目
cd tutorial # 进入项目文件夹
scrapy genspider quotes quotes.toscrape.com # 创建 Spider
运行完 第八行的代码后,就会在 spiders 文件夹中生成一个 quotes.py 文件,就是我们刚刚创建的 Spider
Item Pipeline 的实现:
定义一个类(class),并实现 process_item()方法。process_item()方法必须返回包含数据的字典或Item对象,或者抛出 DropItem 异常。
# 修改 pipelines.py 文件
# 删除掉命令行自动生成的文件内容,增加一个 TexePipeline 类(类名自定义),内容如下
# TextPipline 类实现的是数据的处理
from scrapy.exceptions import DropItem
class TextPipline(object):
def __init__(self):
self.limit = 50
def process_item(self, item, spider):
if item["text]:
if len(item["text"]) > self.limit:
item["text"] = item['text'][0:self.limit].rstrip() + '...'
return item
else:
return DropItem("Missing Text")
本段代码在构造方法中定义了限制长度为 50 , 实现了 process_item() ,其参数是 item 和 spider
。首先该方法判断 item 的 text 属性是否存在,如果不存在,则抛出 DropItem 异常,
如果存在,再判断长度是否大于50,如果大于50,就执行截断字符串拼接省略号操作,再将item 返回。
# 在上一段的代码后面继续写
# 本段代码实现将数据存到 MongoDB 中
import pymongo
class MongoPipeline(object):
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(mongo_uri = crawler.settings.get("MONGO_URI"),
mongo_db = crawler.settings.get("MONGO_DB")
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self, item, spider):
name = item.__class__.__name__
self.db[name].insert(dict(item))
return item
def close_spider(self, spider):
self.client.close()
from_crawler:是一个类方法,用 @classmethod 标识,是一种依赖注入的方式。它的参数就是 crawler ,通过
crawler 我们可以拿到全局配置的每个配置信息。在全局配置 setting.py 。中,我们可以定义 MONGO_URI 和
MONGO_DB 来制定 MongoDB 链接需要的地址和数据库名称,拿到配置信息之后返回类对象即可,所以这个方法主要用来获取
setting.py 中的配置信息的。open_spider: 当 Spider 开启时,这个方法就会被调用,主要用于进行一些初始化操作。
close_spider: 当 Spider 关闭时,这个方法会被调用,主要用于将数据库链接关闭等操作。
process_item:执行数据插入操作。
定义好 TextPipline 和 MongoPipeline 这两个类后,需要在 setting.py 中启用它们。 需要在 setting.py 中加入如下内容:
ITEM_PIPELINES = {
'tutorial.pipelines.TextPipline': 300,
'tutorial.pipelines.MongoPipeline ': 400, }
Spider 类分析
通过命令创建的 scrpay 项目均是继承自 scrapy.spiders.Spider 。这个类是最简单最基本的 Spider 类,其他Spider 必须继承这个类。
scrapy.spiders.Spider 类提供了 start_requests() 方法的默认实现,读取并请求 start_urls 属性,并根据返回的结果调用 parse() 方法姐结果。
它还有以下一些基础属性:
name:爬虫名称,是定义 Spider 名称的字符串, Spider 的名字定义了 Scrapy 如何定位并初始化 Spider , 它必须是唯一的, 我们可以生成多个相同的Spider 实例,数量无限制,但是Name 必须是唯一的。
allowed_domains:允许爬取的域名,是可选配置,不在此范围内的链接不会被跟进爬取。
start_urls:起始Url 列表,当我们没有实现start_requests() 方法时,默认会从这个列表开始抓取。
costom_setting:字典格式,是专属于本Spider 的配置,此设置和覆盖项目全局的设置。此设置必须在初始化钱被更新,必须定义成类变量。
crawler:它是由 from_crawler() 方法设置的,代表的是本spider 类对应的Crawler 对象。Crawler 对象包含了很多项目组件,利用它我们可以获取项目的一些配置信息,如最常见的获取项目的设置信息,即 Settings。
settings:它是一个 Settings 对象,利用它我们可以直接获取项目的全局设置变量。
Spider 常用方法:
start_requests() :此方法用于生成初始请求,它必须返回一个可迭代对象,此方法会默认使用 start_urls 里面的 url 来构造 Request ,而 Request 是GET 请求方式,如果我们像在启动时以POST 方式访问某个站点,可以重写这个方法,发送 POST 请求时使用 FromRequest 即可。
parse() :当 response 没有指定回调函数时,该方法会默认被调用。它负责处理 response ,处理返回结果,并从中提取想要的数据和下一步的请求,然后返回。该方法需要返回一个包含 Request 或者 Item 的可迭代对象。
close() : 当Spider 关闭时,该方法会被调用,在这里一般会定义释放资源的一些操作或者其他收尾操作。
Downloader Middlewares 的用法
可实现的功能:
- 修改 User-Agent
- 处理重定向
- 设置代理IP
- 时报重试
- 设置Cookies …
Downloader Middlewares 的核心方法
- process_request(request, spider)
- process_response(request, response, spider)
- process_exception(request, exception, spider)
process_request(request, spider)
Request 被 Scrapy 引擎调度给 Downloader 之前 process_request() 方法就会被调用,也就是在 Request 从队列里调度出来到 Downloader 下载执行之前,我们都可以用 process_request() 方法对 Request 进行处理。方法的返回值必须为 None、Response 对象、 Request 对象之一,或者抛出IgnoreRequest 异常。
process_request()
process_request() 方法的参数有如下两个:
request:是Request 对象,即被处理的 Request。
spider:是 Spider 对象,即此Request 对应的Spider。
返回类型不同,产生的效果也不同:
- 当返回 None 时, Scrapy 将继续处理该 Request ,接着执行其他 Downloader Middlewares 的 process_request() 方法,一直到 Downloader 把 Request 执行后得到 Response 才结束,这个过程起始就是修改 Request 的过程,不同的 Downloader Middlewares 按照设置的优先级顺序一次对 Request 进行修改,最后送至 Downloader 执行。
- 当返回 Response 对象时, 更低优先级的 Downloader Middlewares 的 process_request() 和 process_exception() 方法就不会被继续调用,每个 Downloader Middlewares 的 process_response() 方法被一次调用。调用完毕后,直接将 Response 对象发送给 Spider 来处理。
- 当返回为Request对象时,更低优先级的Downloader Middleware的process_request()方法会 停止执行。这个Request会重新放到调度队列里,其实它就是一个全新的Request,等待被调 度。如果被 Scheduler 调度了,那么所有的 Downloader Middleware 的 process_request()方法 会被重新按照顺序执行。
- 如果 IgnoreRequest 异常抛出,则所有的 Downloader Middleware 的 process_exception()方法 会依次执行。如果没有一个方法处理这个异常,那么Request的errorback()方法就会回调。 如果该异常还没有被处理,那么它便会被忽略。
process_response()
process_response()方法的参数有如下三个。
Request,是 Request 对象,即此 Response对应的 Request。
response,是 Response 对象,即此被处理的 Response。
spider,是 Spider 对象,即此 Response对应的 Spider。
返回类型不同,产生的效果也不同:
- 当返回为Request对象时,更低优先级的Downloader Middleware的process_response()方法 不会继续调用。该Request对象会重新放到调度队列里等待被调度,它相当于一个全新的 Requesto 然后,该 Request 会被 process_request()方法顺次处理。
- 当返回为Response对象时,更低优先级的Downloader Middleware的process_response()方法 会继续调用,继续对该Response对象进行处理。
- 如果IgnoreRequest异常抛出,贝0 Request的errorback。方法会回调。如果该异常还没有被处 理,那么它便会被忽略。
process_exception ()
process_exception ()方法的参数有如下三个。
request,是Request对象,即产生异常的Request。
exception,是Exception对象,即抛出的异常。
spdier,是 Spider 对象,BP Request对应的 Spider。
下面归纳一下不同的返回值。
- 当返回为None时,更低优先级的Downloader Middleware的process_exception()会被继续顺 次调用,直到所有的方法都被调度完毕。
- 当返回为 Response 对象时,更低优先级的 Downloader Middleware 的 process_exception()方 法不再被继续调用,每个Downloader Middleware的process_response()方法转而被依次调用。
- 当返回为Request对象时,更低优先级的Downloader Middleware的process_exception()也不 再被继续调用,该Request对象会重新放到调度队列里面等待被调度,它相当于一个全新的 Requesto然后,该Request又会被process_request()方法顺次处理。
Downloader Middlewares 实现随机 User-Agent
在 middlewares.py中 添加一个新的类,
import random
class RandomUserAgentMiddleware():
def __init__(self):
self.user_agent = [
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.11) Gecko/2009060310 Linux Mint/6 (Felicia) Firefox/3.0.11',
'Mozilla/5.0 (Windows NT 6.1; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre Firefox/4.0b6pre',
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15'
]
def process_request(self, request, spider):
request.headers["User-Agent"] = random.choice(self.user_agent)
在setting.py 中,将DOWNLOADER_MIDDLEWARES 取消注释并设置为以下内容:
DOWNLOADER_MIDDLEWARES = {
'项目名称.middlewares.RandomUserAgentMiddleware': 543,
}
Scrapy 对接 Selenium
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from scrapy.http import HtmlResponse
from logging import getLogger
class SeleniumMiddleware():
def __init__(self, timeout=None, service_args=[]):
self.logger = getLogger(__name__)
self.timeout = timeout
self.browser = webdriver.PhantomJS(service_args=service_args)
self.browser.set_window_size(1400, 700)
self.browser.set_page_load_timeout(self.timeout)
self.wait = WebDriverWait(self.browser, self.timeout)
def __del__(self):
self.browser.close()
def process_request(self, request, spider):
"""
用 PhantomJS 抓取页面
:param request:
:param spider:
:return:
"""
self.logger.debug('PhantomJS is Stsrting')
page = request.meta.get('page', 1)
try:
self.browser.get(request.url)
if page > 1:
input = self.wait.until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, '#mainsrp-pager dic.form > input')))
submit = self.wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "#mainsrp-pager div.form > span.btn.J_Submit")))
input.clear()
input.send_keys(page)
submit.click()
self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page)))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
return HtmlResponse(url=request.url, body=self.browser.page_source, request=request, encoding='utf-8', status=200)
except TimeoutException:
return HtmlResponse(url=request.url, status=500, request=request)
@classmethod
def from_crawler(cls, crawler):
return cls(timeout=crawler.settings.get("SELENIUM_TIMEOUT"),
service_args=crawler.settings.get("PHANTOMJS_SERVICE_ARGS"))
首先我们在 __init__() 里对一些对象进行初始化,包括PhantomJS. WebDriverWait等对象,
同时 设置页面大小和页面加载超时时间。在process_request()方法中,我们通过Request的meta属性获取当前
需要爬取的页码,调用PhantomJS对象的get()方法访问Request的对应的URL。这就相当于 从Request对象里获取
请求链接,然后再用PhantomJS加载,而不再使用Scrapy里的Downloader。
随后的处理等待和翻页的方法在此不再赘述,和前文的原理完全相同。最后,页面加载完成之后,
我们调用PhantomJS的page_source属性即可获取当前页面的源代码,然后用它来直接构造并返回一个
HtmlResponse对象。构造这个对象的时候需要传入多个参数,如url. body等,这些参数实际上就是它的基础属性。
可以在官方文档査看HtmlResponse对象的结构:
https://doc.scrapy.org/en/latest/topics/ request-response.htmlo
这样我们就成功利用PhantomJS来代替Scrapy完成了页面的加载,最后将 Response返回即可。