Scrapy框架结构及工作原理
组件 | 描述 | 类型 |
---|---|---|
ENGINE | 引擎,框架的核心,其他所有组件在其控制下协同工作 | 内部组件 |
SCHEDULE | 调度器,负责对SPIRDER提交的下载请求进行调度 | 内部组件 |
DOWNLOADER | 下载器,负责下载页面(发送HTTP请求/接收HTTP响应) | 内部组件 |
SPIRDER | 爬虫,负责提取页面中的数据,并产生对新页面的下载请求 | 用户实现 |
MIDDLEWARE | 中间件,负责对Request对象和Response对象进行处理 | 可选组件 |
ITEMPIPELINE | 数据管道,负责对爬取到的数据进行处理 | 可选组件 |
三种对象
对象 | 描述 |
---|---|
REQUEST | Scrapy中的HTTP请求对象 |
RESPONSE | Scrapy中的HTTP响应对象 |
ITEM | 从页面中爬取的一项数据 |
几种对象在框架中的流动过程
- 当spider要爬取某url地址的页面时,需使用该url构造一个request对象,提交给engine(图1中的1)。
- request对象随后进入schedule按某种算法进行排队,之后的某个时刻schedule将其出队,送往Downloader(图1中的2,3,4)
- DOWNLOADER根据Request对象中的URL地址发送一次HTTP请求到网站服务器, 之后用服务器返回的HTTP响应构造出一个Response对象, 其中包含页面的HTML文本(图1中的5)
- Response对象最终会被递送给SPIDER的页面解析函数(构造Request对象时指定) 进行处理, 页面解析函数从页面中提27取数据, 封装成Item后提交给ENGINE, Item之后被送往ITEMPIPELINES进行处理, 最终可能由EXPORTER(图2-1中没有显示) 以某种数据格式写入文件(csv, json) ; 另一方面、页面解析函数还从页面中提取链接(URL) , 构造出新的Request对象提交给ENGINE(图1中的6、 7、8)
理解了框架中的数据流,也就理解了Scrapy爬虫的工作原理
2、编写spider
2.1、request对象
1、Request对象用来描述一个HTTP请求, 下面是其构造器方法的参数列表
Request(url,
callback,
headers,
body, meta, priority=0, errback)
2、scrapy.request( )的参数
官方文档:https://doc.scrapy.org/en/latest/topics/request-response.html
2.2、response对象
- 当一个页面下载完成时,下载器依据HTTP响应头部中的Content-Type信息创建某个Response的子类对象。
- 下面介绍HtmlResponse对象的常用属性,前两个方法用于提取数据,后一个方法用于构造绝对url。
1、xpath(query) :使用XPath选择器在Response中提取数据,实际上它是response.selector.xpath方法的快捷方式
2、css(query):使用CSS选择器在Response中提取数据,实际上它是response.selector.css方法的快捷方式
3、urljoin(url):用于构造绝对url。当传入的url参数是一个相对地址时,根据response.url计算出相应的绝对url。
例如,response.url为http://www.example.com/a,url为b/index.html,调用response.urljoin(url)的结果为 http://www.example.com/a/b/index.html。
其他参数详情参见:书《精通Scrapy网络爬虫框架》P16
2.3 spider开发流程
- Scrapy框架提出以下问题让用户在Spider子类中作答
1、爬虫从哪个或哪些页面开始爬取?
2、对于一个已下载的页面,提取其中的哪些数据?
3、爬取完当前页面后,接下来爬取哪个或哪些页面? - 为爬虫设置其实爬取地址的方式
1、定义start_urls属性
2、实现start_requests方法
2.4 实现一个spider只需要下面4个步骤:
- 步骤 01 继承scrapy.Spider。
- 步骤 02 为Spider取名。
- 步骤 03 设定起始爬取点。
- 步骤 04 实现页面解析函数。
3、使用selector提取数据
3.1、selector对象
- 提取数据的方法:
调用Selector或SelectorLis对象的以下方法可将选中的内容提取:
1、extract()
2、re()
3、extract_first() (SelectorList专有)
4、re_first() (SelectorList专有)
3.2、Response内置selector
- 页面测试
from scrapy.http import HtmlResponse
response = HtmlResponse(url='http://www.example.com', body=body, encoding='utf8')
response.selector
3.3、XPath
1、XPath即XML路径语言(XML Path Language),它是一种用来确定xml文档中某部分位置的语言。
2、xml文档的节点有多种类型,其中最常用的有以下几种:
2.1、根节点 整个文档树的根。
2.2、元素节点 html、body、div、p、a。
2.3、属性节点 href。
2.4、文本节点 Hello world、Click here。
3、节点间的关系有以下几种:
3.1、父子 body是html的子节点,p和a是div的子节点。反过来,div是p和a的父节点。
3.2、兄弟 p和a为兄弟节点。
3.3、祖先/后裔 body、div、p、a都是html的后裔节点;反过来html是body、div、p、a的祖先节点。
4、基础语法
详情参见:http://www.w3school.com.cn/xpath/xpath_syntax.asp
4、使用Item封装数据
4.1、Item和Field(利用BookItem代替字典)
- Item基类
自定义数据类(如BookItem)的基类。 - Field类
用来描述自定义数据类包含哪些字段(如name、price等)。
from scrapy import Item, Field
class BookeItem(Item):
name = Field()
price = Field()
# Item支持字典接口,因此BookItem在使用上和Python字典类似,可按以下方式创建BookItem对象:
# 方法一:
>>> book1 = BookItem(name='Needful Things', price=45.0)
>>> book1
{'name': 'Needful Things', 'price': 45.0}
# 方法二:
>>> book2 = BookItem()
>>> book2
{}
>>> book2['name'] = 'Life of Pi'
>>> book2['price'] = 32.5
{'name': 'Life of Pi', 'price': 32.5}
- 修改之前的BooksSpider,使用BookItem替代Python字典,代码如下:
# 改用parse_book作为回调函数
def parse_book(self, response):
# 提取数据
# 每一本书的信息在中
# css()方法找到所有这样的article元素,并依次迭代
for book in response.css('article.product_pod'):
# 书名信息在article>h3>a元素的title属性里
name = book.xpath('./h3/a/@title').extract_first()
# 书价信息在的TEXT中
price = book.css('p.price_color::text').extract_first()
book = BookeItem()
book['name'] = name
book['price'] = price
yield book
4.2、扩展Item子类
- 新建一个ForeignBookItem类,增添一个译者字段,其他的继承BookItem类,代码如下:
from scrapy import Item, Field
class BookeItem(Item):
name = Field()
price = Field()
class ForeignBookItem(BookeItem):
translator=Field()
4.3、Field元数据
- field()的元数据可以是列表、字符串或者函数
class BookItem(Item):
authors = Field(serializer=lambda x: '|'.join(x))
5、使用Item Pipeline处理数据
- 以下是Item Pipeline的几种典型应用:
- 清洗数据
- 验证数据的有效性
- 过滤掉重复的数据
- 将数据存入数据库
5.1、Item Pipline
#######5.1.1、实现Item Pipline
- item pipeline常用方法:
- process_item(item, spider):处理数据
- open_spider(self, spider):打开Spider打开时(处理数据前)回调该方法,通常该方法用于在开始处理数据之前完成某些初始化工作,如连接数据库。
- close_spider(self, spider):关闭Spider关闭时(处理数据后)回调该方法,通常该方法用于在处理完所有数据之后完成某些清理工作,如关闭数据库。
- from_crawler(cls, crawler):调回创建Item Pipeline对象时回调该类方法。通常,在该方法中通过crawler.settings读取配置,根据配置创建Item Pipeline对象。
代码如下:
class PriceConverterPipline(object):
exchange_rate = 8.5309
def process_item(self, item, spider):
# 提取item的price字段
# 去掉前面的英镑符号,转换为float类型,乘以汇率
price = float(item['price'][1:]) * self.exchange_rate
# 保留两位小数,赋值回item的price字段
item['price'] = '¥%.2f' % price
return item
上述代码中的process_item方法实现非常简单, 将书籍的英镑价格转换为浮点数, 乘以汇率并保留2位小数, 然后赋值回item的price字段, 最后返回被处理过
的item。
#######5.1.2、启用Item Pipline
在Scrapy中, Item Pipeline是可选的组件, 想要启用某个(或某些) Item Pipeline, 需要在配置文件settings.py中进行配置:
ITEM_PIPELINES = {
'scrapyStudy.pipelines.PriceConverterPipline': 300,
}
其中每一项的键是每一个Item
Pipeline类的导入路径, 值是一个0~1000的数字,同时启用多个Item Pipeline时, Scrapy根据这些数值决定各Item Pipeline处理数据的先后次序, 数值小的在前。
#######5.2.1 过滤重复数据
以书名作为主键(实际应以ISBN编号为主键, 但是仅爬取了书名和价格) 进行去重, 实现DuplicatesPipeline代码如下:
class DuplicatesPipeline(object):
def __init__(self):
self.book_set = set()
def process_item(self, item, spider):
name = item['name']
if name in self.book_set:
raise DropItem("Duplicate book find : %s%" % item)
self.book_set.add(name)
return item
#######5.2.2 将数据存储到Mysql
代码如下:
class MysqlPipline(object):
def open_spider(self, spider):
db = 'spider'
host = '192.168.100.173'
port = 3306
user = 'root'
passwd = '05zf*+VCT0YeIfx'
self.db_conn = pymysql.connect(host=host, port=port, db=db, user=user, passwd=passwd, charset='utf8')
self.db_cur = self.db_conn.cursor()
def close_spider(self, spider):
# 爬取完全部的数据后调用commit
# self.db_conn.commit()
self.db_conn.close()
def process_item(self, item, spider):
if isinstance(item, BookeItem):
self.insert_book(item)
return item
def insert_book(self, item):
values = (
item['name'],
item['price'])
sql = 'INSERT IGNORE INTO spider_book_test (name,price) VALUES (%s,%s)'
self.db_cur.execute(sql, values)
self.db_conn.commit()