Scrapy 框架基础入门文档
参考文档
参考文档1:https://www.jianshu.com/p/8e78dfa7c368
参考文档2:https://blog.csdn.net/ck784101777/article/details/104468780
参考文档3:https://www.cnblogs.com/wt7018/p/11749778.html
前言
本篇文档只是对 Scrapy 框架的大致了解,结合了数篇网上对于 Scrapy 入门文档,需要了解该框架用到的一些知识点 及一些入门案例的使用。
一、scrapy框架简介
Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛
框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便
二、scrapy架构图
crapy 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)
三、安装 scrapy 框架
pip install scrapy
四、Scrapy 常用命令
命令 | 说明 | 格式 |
---|---|---|
startproject | 创建一个工程 | scrapy startproject < name > [ dir ] |
genspider | 创建一个爬虫 | scrapy genspider [ options ] < name > < domain > |
settings | 获取爬虫配置信息 | scrapy settings [ options ] |
crawl | 运行一个爬虫 | scrapy crawl < spider > |
list | 列出工程中所有爬虫 | scrapy list |
shell | 启动 URL 调试命令行 | scrapy shell [ url ] |
五、关于 settings.py 文件中的常用的参数
1、ROBOTSTXT_OBEY 参数
通俗来说, robots.txt 是遵循 Robot 协议 的一个文件,它保存在网站的服务器中,它的作用是,告诉搜索引擎爬虫,本网站哪些目录下的网页 不希望 你进行爬取收录。在 Scrapy 启动后,会在第一时间访问网站的 robots.txt 文件,然后决定该网站的爬取范围。
当然,我们并不是在做搜索引擎,而且在某些情况下我们想要获取的内容恰恰是被 robots.txt 所禁止访问的。所以,某些时候,我们就要将此配置项设置为 False ,拒绝遵守 Robot 协议 !
ROBOTSTXT_OBEY = False
2、BOT_NAME:项目名称
3、USER_AGENT:客户端
默认是注释的,这个东西非常重要,如果不写很容易被判断为电脑,简单点写一个Mozilla/5.0即可
USER_AGENT = 'Mozilla/5.0'
4、CONCURRENT_REQUESTS:最大并发数,很好理解,就是同时允许开启多少个爬虫线程
5、DOWNLOAD_DELAY:下载延迟时间,单位是秒,控制爬虫爬取的频率,根据你的项目调整,不要太快也不要太慢,默认是3秒,即爬一个停3秒,设置为1秒性价比较高,如果要爬取的文件较多,写零点几秒也行
6、COOKIES_ENABLED:是否保存COOKIES,默认关闭,开机可以记录爬取过程中的COKIE,非常好用的一个参数
7、DEFAULT_REQUEST_HEADERS:默认请求头,上面写了一个USER_AGENT,其实这个东西就是放在请求头里面的,这个东西可以根据你爬取的内容做相应设置。
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
8、ITEM_PIPELINES:项目管道,300为优先级,越低越爬取的优先度越高
ITEM_PIPELINES = {
'mySpider.pipelines.MyspiderPipeline': 300,
}
9、日志相关变量
LOG_LEVEL= ""
LOG_FILE="日志名.log"
日志等级分为
1.DEBUG 调试信息
2.INFO 一般信息
3.WARNING 警告
4.ERROR 普通错误
5.CRITICAL 严重错误
案例:
LOG_LEVEL = "INFO"
LOG_FILE = "Spider.log"
六、关于yeild函数介绍
简单地讲,yield 的作用就是把一个函数变成一个 generator(生成器),带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,带有yeild的函数遇到yeild的时候就返回一个迭代值,下次迭代时, 代码从 yield 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行, 直到再次遇到 yield。
通俗的讲就是:在一个函数中,程序执行到yield语句的时候,程序暂停,返回yield后面表达式的值,在下一次调用的时候,从yield语句暂停的地方继续执行,如此循环,直到函数执行完。
七、关于 xpath 介绍
参考:https://www.cnblogs.com/wt7018/p/11749778.html
XPath 是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。
1、方式一 HtmlResponse (推荐)
from scrapy.http import HtmlResponse
html = """
html网页
"""
# 注意这个url是任意的,但是必须填写
response = HtmlResponse(url='http://example.com', body=html, encoding='utf-8')
ret = response.xpath('//ul/li[@class="item-0"]/a[@id="i2"]/text()').extract_first()
print(ret)
2、方法二 Selector
from scrapy.http import HtmlResponse
from scrapy.selector import Selector
html = """
html网页
"""
response = HtmlResponse(url='http://example.com', body=html, encoding='utf-8')
selector = Selector(response)
ret = selector.xpath('//ul/li[@class="item-0"]/a[@id="i2"]/text()').extract_first()
print(ret)
3、常用的 选择器 案例
xpath('//a') # 所有a标签(子孙后代)
xpath('//a[2]') # 所有a标签,按索引找第二个
xpath('//a[@id]') # 所有a标签,并且含有id属性
xpath('//a[@id="i1"]') # 所有a标签,并且属性id='i1'
xpath('//a[@href="link.html"][@id="i1"]') # 所有a标签,属性href="link.html" 而且 id="i1"
xpath('//a[contains(@href, "link")]') # 所有a标签,属性href的值包含"link"
xpath('//a[starts-with(@href, "link")]') # 所有a标签,属性href的值以"link"开头
xpath('//a[re:test(@id, "i\d+")]') # 所有a标签 属性id的值 符合正则表达式"i\d+"的规则
xpath('//a[re:test(@id, "i\d+")]/text()').extract() # 所有a标签,取text的值
xpath('//a[re:test(@id, "i\d+")]/@href').extract() # 所有a标签,取href的属性值
xpath('/html/body/ul/li/a/@href').extract() # 取所有的值
xpath('//body/ul/li/a/@href').extract_first() # 取第一个值
八、Hello World 小案例
1、创建爬虫项目,命令:scrapy startproject 项目名称
2、创建爬虫文件,命令:scrapy genspider 文件名称 域名
创建完成后会自动生成一些文件
目标网站分析需要提取的数据,在item.py文件中添加字段
Item 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误
创建后目录大致如下
|-ProjectName #项目文件夹
|-ProjectName #项目目录
|-items.py #定义数据结构
|-middlewares.py #中间件
|-pipelines.py #数据处理
|-settings.py #全局配置
|-spiders
|-__init__.py #爬虫文件
|-scrapy.cfg #项目基本配置文件
在爬虫文件中会,默认生成下列代码
import scrapy
class TestSpider(scrapy.Spider):
# 爬虫名称
name = 'test'
# 设置允许爬取的域(可以指定多个)
allowed_domains = ['baidu.com']
# 设置起始url(设置多个)
start_urls = ['http://baidu.com/']
def parse(self, response):
'''
是一个回调方法,起始url请求成功后,会回调这个方法
:param response: 响应结果
:return:
'''
pass
在parse方法中做数据的提取
def parse(self, response):
'''
在parse回调方法中
step1;提取目标数据
step2;获取新的url
:param response: 请求的响应结果
:return:
'''
print(response.status)
#response.xpath(): 使用xpath语法,得到的是selectorlist对象
# response.css(): 使用css选择器,得到的是selectorlist对象
# extract(): 将selector 序列化为unicode字符串
# step1;提取目标数据
# 获取分类列表
tags = response.xpath('//div[@class="Taright"]/a')
# tags = response.css('.Taright a')
for tag in tags:
# 实例化一个item,用来存储数据
tag_item = MyspiderItem()
# 获取网站分类的名称
# tagname = tag.xpath('./text()')[0].extract()
tagname = tag.xpath('./text()').extract_first('')
tag_item['tagname'] = tagname
# 使用css取值(文本)
# tagname = tag.css('::text').extract_first('')
# 获取网站分类的首页url地址
# first_url = tag.xpath('./@href')[0].extract()
first_url = tag.xpath('./@href').extract_first('')
tag_item['firsturl'] = first_url
# css取值(属性)
# first_url = tag.css('::attr(href)').extract_first('')
print(tag_item)
# print(type(tagname),type(first_url))
# print(tagname,first_url)
# 将获取到的数据交给管道处理
yield tag_item
# http://top.chinaz.com/hangye/index_yule_yinyue.html
# http://top.chinaz.com/hangye/index_yule_yinyue_2.html
'''
url,设置需要发起请求的url地址
callback=None,设置请求成功后的回调方法
method='GET',请求方式,默认为get请求
headers=None,设置请求头,字典类型
cookies=None,设置cookies信息,模拟登录用户,字典类型
meta=None,传递参数(字典类型)
encoding='utf-8',设置编码
dont_filter=False, 是否去重,默认为false,表示去重
errback=None, 设置请求失败后的回调
'''
yield scrapy.Request(first_url,callback=self.parse_tags_page)
run, 执行项目
方式1:python 文件执行
from scrapy import cmdline
cmdline.execute('scrapy crawl test'.split())
方式2:cmd 命令行执行
scrapy crawl test
九、Item pipeline(管道文件)使用
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,这些Item Pipeline组件按定义的顺序处理Item。
每个Item Pipeline都是实现了简单方法的Python类,比如决定此Item是丢弃而存储。以下是item pipeline的一些典型应用:
验证爬取的数据(检查item包含某些字段,比如说name字段)查重(并丢弃)
将爬取结果保存到文件或者数据库中
items 文件参考案例
import scrapy
class MyspiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
class WebInfoItem(scrapy.Item):
title = scrapy.Field()
company = scrapy.Field()
salary = scrapy.Field()
job_adv = scrapy.Field()
job_bt = scrapy.Field()
# SQL 语句也可以在这边写
def get_insert_sql(self):
sql = "insert into test(title,company,salary,job_adv,job_bt)values(%s,%s,%s,%s,%s)"
data = (self['title'], self['company'], self['salary'], self['job_adv'], self['job_bt'])
return sql, data
item pipiline组件是一个独立的Python类,其中process_item()方法必须实现
class SomethingPipeline(object):
def __init__(self):
# 可选实现,做参数初始化等
# doing something
def process_item(self, item, spider):
# item (Item 对象) – 被爬取的item
# spider (Spider 对象) – 爬取该item的spider
# 这个方法必须实现,每个item pipeline组件都需要调用该方法,
# 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。
return item
def open_spider(self, spider):
# spider (Spider 对象) – 被开启的spider
# 可选实现,当spider被开启时,这个方法被调用。
def close_spider(self, spider):
# spider (Spider 对象) – 被关闭的spider
# 可选实现,当spider被关闭时,这个方法被调用
启用一个Item Pipeline组件 为了启用Item Pipeline组件,必须将它的类添加到 settings.py文件ITEM_PIPELINES 配置,就像下面这个例子:
ITEM_PIPELINES = {
# 'mySpider.pipelines.SomePipeline': 300,
"mySpider.pipelines.MyspiderPipeline":300
}
分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内(0-1000随意设置,数值越低,组件的优先级越高
十、定制图片下载管道
在settings.py中设置 IMAGES_STORE 设置为一个有效的文件夹,用来存储下载的图片
IMAGES_STORE = '/xxx/xxx/xxx'
import pymysql, scrapy,os,pymongo
from scrapy.contrib.pipeline.images import ImagesPipeline
from mySpider.items import WebInfoItem,MyspiderItem
from scrapy.utils.project import get_project_settings
#os.rename()
# 获取settimgs.py文件中的设置信息
images_store = get_project_settings().get('IMAGES_STORE')
class Imagepipeline(ImagesPipeline):
# 实现两个方法
def get_media_requests(self, item, info):
'''
根据图片的url地址,构造requset请求
:param item:
:param info:
:return:
'''
# 判断item来自哪个模块
if isinstance(item, WebInfoItem):
# 获取图片地址
image_url = 'http:' + item['coverimage']
print('获取图片地址', image_url)
yield scrapy.Request(image_url)
# 如果有多个图片地址
# image_urls = 'http:' + item['coverimage']
# return [scrapy.Request(x) for x in image_urls]
#
def item_completed(self, results, item, info):
'''
图片下载之后的回调方法
:param results: [(True/False,{'path':'图片下载之后的存储路径','url':'图片url地址','ckecksum':'hash加密的一个字符串'})]
:param item:
:param info:
:return:
'''
if isinstance(item, WebInfoItem):
paths = [result['path'] for status, result in results if status]
print('图片下载结果', results)
if len(paths) > 0:
print('图片下载成功')
# 使用rename方法修改图片名称
os.rename(images_store+'/'+paths[0],images_store+'/'+item['title']+'.jpg')
image_path = images_store+'/'+item['title']+'.jpg'
print('修改之后图片路径:',image_path)
item['localimagepath'] = image_path
else:
# 如果没有成功获取到图片,将这个item丢弃
from scrapy.exceptions import DropItem
raise DropItem('没有获取到图片,遗弃item')
return item
十一、虫数据持久化
方式一:将数据存入mongodb
settings.py文件: 设置文件,在这里设置User-Agent,激活管道文件等...
ITEM_PIPELINES = {
'douban.pipelines.MongoDBpipeline': 300,
}
MONGODB 主机名
MONGODB_HOST = '127.0.0.1'
MONGODB 端口号
MONGODB_PORT= 27017
数据库名称
MONGODB_DBNAME = "Douban"
存储数据的表名称
MONGODB_SHEETNAME= "doubanmovies"
往mongodb数据库中插入数据
class MongoDBpipeline(object):
def __init__(self):
# 创建mongodb的数据库连接
mongo_client = pymongo.MongoClient(
host='127.0.0.1',port=27017
)
# 获取要操作的数据库
self.db = mongo_client['chinaz']
@classmethod
def from_crawler(cls,crawler):
MONGO_HOST = '127.0.0.1'
MONGO_PORT = 27017
MONGO_DB = 'chinaz'
def process_item(self, item, spider):
'''
:param item:
:param spider:
:return:
'''
# 往哪个集合下插入数据
col_name = item.get_mongodb_collectionname()
col = self.db[col_name]
# 往集合下插入什么数据
dict_data = dict(item)
try:
col.insert(dict_data)
print('数据插入成功')
except Exception as err:
print('插入数据失败',err)
def close_spider(self,spider):
self.mongo_client.close()
print(spider.name,'爬虫结束了')
方式二:将数据存入mysql数据库
settings.py文件: 设置文件,在这里设置User-Agent,激活管道文件等...
ITEM_PIPELINES = {
'douban.pipelines.DoubanPipeline': 300,
}
关于数据库的相关配置
MYSQL_HOST = '127.0.0.1'
MYSQL_PORT = 3306
MYSQL_USER = ''
MYSQL_PWD = ''
MYSQL_DB = ''
pipelines.py管道文件
往数据库里插数据
class MysqlPipeline(object):
def __init__(self):
'''
初始化方法
'''
# 创建数据库链接
self.client = pymysql.Connect(
'127.0.0.1', 'sevattal', '12345678',
'sevattal_scrapy', 3306, charset='utf8'
)
# 创建游标
self.cursor = self.client.cursor()
def open_spider(self, spider):
'''
爬虫启动的时候回调用一次
:param spider:
:return:
'''
print('爬虫开启')
pass
def process_item(self, item, spider):
'''
这个方法必须实现,爬虫文件中所有的item 都会经过这个方法
:param item: 爬虫文件传递过来的item对象
:param spider: 爬虫文件实例化的对象
:return:
'''
# 存储到本地json文件
data_dict = dict(item)
# import json
# json_data = json.dumps(data_dict,ensure_ascii=False)
# self.file.write(json_data+'\n')
# 使用isisinstance判断 item 要存储的表
# if isinstance(item,WebInfoItem):
# print('网站信息')
# tablename = 'webinfo'
# elif isinstance(item,MyspiderItem):
# print('网站分类信息')
# tablename = 'tags'
# #
# # 往数据库里写
# sql = """
# insert into %s(%s)
# values (%s)
# """ % (tablename,','.join(data_dict.keys()), ','.join(['%s'] * len(data_dict)))
sql,data = item.get_insert_sql_data(data_dict)
try:
self.cursor.execute(sql, list(data_dict.values()))
self.client.commit()
except Exception as err:
self.client.rollback()
print(err)
# 如果有多个管道文件,一定要return item , 否则下一管道无法接收到item
print('经过了管道')
return item
def close_spider(self, spider):
'''
爬虫结束的时候调用一次
:param spider:
:return:
'''
# self.file.close()
self.client.close()
self.cursor.close()
print('爬虫结束')
异步插入数据库
import pymysql
twisted是一个异步的网络框架,这里可以帮助我们
实现异步将数据插入数据库
adbapi里面的子线程会去执行数据库的阻塞操作,
当一个线程执行完毕之后,同时,原始线程能继续
进行正常的工作,服务其他请求。
from twisted.enterprise import adbapi
#异步插入数据库
class DoubanPipeline(object):
def __init__(self,dbpool):
self.dbpool = dbpool
#使用这个函数来应用settings配置文件。
@classmethod
def from_crawler(cls, crawler):
parmas = {
'host':crawler.settings['MYSQL_HOST'],
'user':crawler.settings['MYSQL_USER'],
'passwd':crawler.settings['MYSQL_PASSWD'],
'db':crawler.settings['MYSQL_DB'],
'port':3306,
'charset':'utf8',
}
# **表示字典,*tuple元组,
# 使用ConnectionPool,起始最后返回的是一个ThreadPool
dbpool = adbapi.ConnectionPool(
'pymysql',
**parmas
)
return cls(dbpool)
def process_item(self, item, spider):
#这里去调用任务分配的方法
query = self.dbpool.runInteraction(
self.insert_data_todb,
item,
spider
)
#数据插入失败的回调
query.addErrback(
self.handle_error,
item
)
#执行数据插入的函数
def insert_data_todb(self,cursor,item,spider):
insert_str,parmas = item.insertdata()
cursor.execute(insert_str,parmas)
print('插入成功')
def handle_error(self,failure,item):
print(failure)
print('插入错误')
#在这里执行你想要的操作
def close_spider(self, spider):
self.pool.close()
十二、通用爬虫
CrawlSpider它是Spider的派生类,Spider类的设计原则是只爬取start_url列表中的网页,而CrawlSpider类定义了一些规则Rule来提供跟进链接的方便的机制,从爬取的网页结果中获取链接并继续爬取的工作
创建通用爬虫文件命令: scrapy genspider -t crawl 爬虫文件 域名
例:chinaz
class TestSpider(CrawlSpider):
# 爬虫名称
name = 'test'
# 设置允许爬取的域
allowed_domains = ['chinaz.com']
# 设置起始url
start_urls = ['http://top.chinaz.com/hangyemap.html']
# rules:存放定制的规则获取连接的规则对象(可以是一个列表也可以是一个元组)
# 根据规则提取到的所有链接,都会由crawlspider 构建request对象并且交给引擎处理
'''
LinkExtractor:设置提取链接的规则(正则表达式)
allow=(), : 设置允许提取的目标url
deny=(), : 设置不允许提取的目标url(优先级比allow高)
allow_domains=(), :设置允许提取url的域
deny_domains=(), : 设置不允许提取url的域(优先级比allow_domains高)
restrict_xpaths=(),:根据xpath语法,定位到某一标签下提取链接
restrict_css=(),:根据css选择器,定位到某一标签下提取链接
unique=True, :如果出现多个相同的url,只会保留一个
strip=True:默认为true,表示去除url首尾空格
'''
'''
link_extractor,:link_extractor对象
callback=None, :设置回调函数
follow=None, :是否设置跟进
process_links=None, :可以设置回调函数,对所有提取到的url进行拦截
process_request=identity:可以设置回调函数,对request对象进行拦截
'''
#http://top.chinaz.com/hangye/index_yule_yinyue.html
#http://top.chinaz.com/hangye/index_shopping_dianshang.html
rules = (
# 规则对象
Rule(LinkExtractor
(allow=r'http://top.chinaz.com/hangye/index_.*?html',
restrict_xpaths=('//div[@class="Taright"]','//div[@class="ListPageWrap"]')),
callback='parse_item',
follow=True),
)