1 scrapy
1.1 简介
scrapy
框架Python
编写 ,是 一个快速、高层次的屏幕抓取和 web 抓取框架,用于抓取 web
站点并从页面中提取结构化的数据。Scrapy
用途广泛,可以用于数据挖掘、监测和自动化测试,还有高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式等等
1.1.1 scrapy原理
scrapy有五大核心组件:
-
引擎
:用来处理整个系统的数据流处理,触发事务(框架核心) -
调度器
:用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回,可以想象成一个URL
的优先队列,由它来决定下一个喜欢的网址是什么,同时去除重复的网址 -
下载器
:用于下载网页内容,并将网页内容返回,scrapy下载是建立在twisted
这个高效的异步模型上的 -
爬虫
:爬虫主要干活的,用于从特定网页中提取自己想要的信息,用户也可以从中爬取出链接,让Scrapy爬取下一个页面 -
管道
:负责处理页面中提取的实体,主要功能是持久化实体,验证实体的有效性,清除不需要信息,当页面被爬虫解析后,将解析发送到项目管道,并经过几个特定次序处理数据
1.2 环境安装
mac
或者linux
系统直接:pip install scrapy
windows
系统,会比较麻烦:
-
首先安装
wheel
:pip install wheel
-
下载
twisted
:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted,下载对应的whl
文件。如下图,cp
后面是python
版本,amd64
代表64
位,划线的是我要下载的,点击下载 -
在下载后的
twisted
文件夹内执行pip
命令来安装:pip install Twisted-20.3.0-cp38-cp38-win_amd64.whl
如果安装报错:
ERROR: Exception:
Traceback (most recent call last):
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\base_command.py", line 173, in _main
status = self.run(options, args)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\req_command.py", line 203, in wrapper
return func(self, options, args)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\commands\install.py", line 315, in run
requirement_set = resolver.resolve(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\resolver.py", line 75, in resolve
collected = self.factory.collect_root_requirements(root_reqs)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 471, in collect_root_requirements
req = self._make_requirement_from_install_req(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 433, in _make_requirement_from_install_req
cand = self._make_candidate_from_link(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 204, in _make_candidate_from_link
self._link_candidate_cache[link] = LinkCandidate(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 295, in init
super().init(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 156, in init
self.dist = self._prepare()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 227, in _prepare
dist = self._prepare_distribution()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 305, in _prepare_distribution
return self._factory.preparer.prepare_linked_requirement(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 508, in prepare_linked_requirement
return self._prepare_linked_requirement(req, parallel_builds)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 570, in _prepare_linked_requirement
dist = _get_prepared_distribution(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 61, in _get_prepared_distribution
return abstract_dist.get_pkg_resources_distribution()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\distributions\wheel.py", line 26, in get_pkg_resources_distribution
with ZipFile(self.req.local_file_path, allowZip64=True) as z:
File "zipfile.py", line 1257, in init
File "zipfile.py", line 1324, in _RealGetContents
zipfile.BadZipFile: File is not a zip file
可能是文件受损,重新下载执行上述命令即可成功
- 安装
pywin32
:pip install pywin32
- 安装
scrapy
:pip install scrayp
最后测试是否安装成功,在DOS
终端里录入scrapy
指令,没有报错即表示安装成功
1.3 使用scrapy
1.3.1 使用步骤
- 创建工程:
scrapy startproject ProjectName
- 创建爬虫文件
先进入到工程目录中,然后执行命令scrapy genspider spiderFileName www.xxx.com
,就会在spriders
子目录中创建一个爬虫文件
例如执行:scrapy genspider firstSpiderFile www.xxx.com
会生成一个文件firstSpiderFile.py 且内部有后缀为Spider
的类allowed_domains
:允许的域名,限定start_urls
列表中哪些url
可以发送请求,如果注释掉,会全部发送
import scrapy
class FirstspiderfileSpider(scrapy.Spider):
#爬虫文件名字,爬虫源文件一个唯一标识
name = 'firstSpiderFile'
#允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
#allowed_domains = ['www.baidu.com']
#起始的url列表:列表中的存放的url被scrapy会自动发送请求
start_urls = ['http://www.baidu.com/']
#用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
def parse(self, response):
print(response)
- 执行工程,输入爬虫文件
spiderFileName
:scrapy crawl spiderFileName
- 在爬虫文件中添加相关代码
注意:
在爬虫文件使用xpath
解析时,返回可能不是字符串,而是Selector
对象,就可以用extract()
方法解析`
1.4 持久化存储
1.4.1 基于终端指令
基于终端指令,只可以将parse
方法的返回值存储到本地的文本文件中,但是只支持一下几种格式:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
使用如下命令,就是在执行命令增加一个-o
参数:
scrapy crawl spiderFileName -o 文件路径+文件名
import scrapy
class FirstspiderfileSpider(scrapy.Spider):
#爬虫文件名字,爬虫源文件一个唯一标识
name = 'firstSpiderFile'
#允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
#allowed_domains = ['www.baidu.com']
#起始的url列表:列表中的存放的url被scrapy会自动发送请求
start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']
#用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
def parse(self, response):
div_list = response.xpath('//div[@class="shici_list_main"]')
all_data=[]
for div in div_list:
#此处使用相对路径
shici_name=div.xpath("h3/a/text()")[0].extract()
# print(shici_name)
shici_text_list=div.xpath("div//text()").extract()
shici_text_list = "".join(shici_text_list)
dict={
'title':shici_name,
"content":shici_text_list
}
all_data.append(dict)
return all_data
注意:
在爬虫文件的parse
方法需要有返回值来保存文件
1.4.2 基于管道
1.4.2.1 基于管道步骤
基于管道大致步骤:(即把要写的代码写到items.py
文件中)
- 在
item类
中定义相关的属性(items.py
) - 把要解析的数据封装存储到
item类
型对象(items.py
) - 将
item类
的数据对象items.py
提交给管道pipelines.py
进行持久化存储操作 - 在管道类的
process_item
中药将其接收到item
对象中存储的数据进行持久化存储操作 - 在配置文件
settings.py
中开启管道
1.4.2.2 基于管道操作
在item类
中定义相关的属性
import scrapy
class FirstItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title=scrapy.Field()
content=scrapy.Field()
把要解析的数据封装存储到item类
型对象注意:
此处的爬虫文件是不需要return
但是需要yield
import scrapy
from first.items import FirstItem
class FirstspiderfileSpider(scrapy.Spider):
#爬虫文件名字,爬虫源文件一个唯一标识
name = 'firstSpiderFile'
#允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
#allowed_domains = ['www.baidu.com']
#起始的url列表:列表中的存放的url被scrapy会自动发送请求
start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']
#用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
def parse(self, response):
div_list = response.xpath('//div[@class="shici_list_main"]')
all_data=[]
for div in div_list:
#此处使用相对路径
shici_name=div.xpath("h3/a/text()")[0].extract()
# print(shici_name)
shici_text_list=div.xpath("div//text()").extract()
shici_text_list = "".join(shici_text_list)
item = FirstItem()
item['title']=shici_name
item['content']=shici_text_list
yield item
pipelines.py
开始持久化操作注意:
在process_item
中的return item
,会把item
传递给下一个即将被执行的管道类,不管后面有没有管道尽量都return
from itemadapter import ItemAdapter
class FirstPipeline(object):
fp =None
#重写父类一个方法:该方法只在开始爬虫的时候被调用一次
def open_spider(self,spider):
print("开始爬虫..............")
self.fp=open('./test123.txt','w',encoding='utf-8')
#专门用来处理item类型对象
#可以接受爬虫文件体积过来的item对象
#该方法每接受一个item对象就会被调用一次
def process_item(self, item, spider):
title=item['title']
content=item['content']
self.fp.write(title+":"+content+'\n')
return item
#继续重写父类方法
def close_spider(self,spider):
print('结束爬虫打印................')
self.fp.close()
在配置文件settings.py
中开启管道
ITEM_PIPELINES = {
#300表示执行优先级,数值越小,优先级越高
'first.pipelines.FirstPipeline': 300,
}
1.4.3 多渠道存储
如果要把爬取的数据分别存在本地和数据库中,那么就在pipelines.py
中写多个管道操作类
在pipelines.py
文件中增加如下类
class MysqlPipeline(object):
conn =None
curcos=None
#重写父类一个方法:该方法只在开始爬虫的时候被调用一次
def open_spider(self,spider):
print("mysql开始爬虫..............")
self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='root',db='test',charset='utf-8')
#专门用来处理item类型对象
#可以接受爬虫文件体积过来的item对象
#该方法每接受一个item对象就会被调用一次
def process_item(self, item, spider):
title=item['title']
content=item['content']
self.curcos=self.conn.cursor()
self.curcos.execute('insert into test values("%s","%s")%(item["author"],item["content"]) ')
self.curcos.commit()
# 会把item传递给下一个即将被执行的管道类
return item
#继续重写父类方法
def close_spider(self,spider):
print('结束mysql爬虫...............')
self.curcos.close()
self.conn.close()
修改settings.py
文件
ITEM_PIPELINES = {
#300表示执行优先级,数值越小,优先级越高
'first.pipelines.FirstPipeline': 300,
'first.pipelines.MysqlPipeline': 301,
}
1.5 全站数据爬取
全站数据爬取就是将网站中某个板块下的全部页码对应的页码数据进行爬取
实现方式:
- 将所有页面url添加到start_urls列表中
- 自行手动请求,需要手动发送请求:
yield scrapy.Request(url,callback)
其中的callback
是专门用于数据解析的
具体操作在爬虫文件中如下:
class XiaohuaSpider(scrapy.Spider):
name='xiaohua'
start_urls=['http://test.com/meinvxiaohua/']
url='http://test.com/meinvxiaohua/?query=python&page=%d.html'
def parse(self, response):
li_list=response.xpath('//*[@id="content"]/div[2]')
for li in li_list:
img_name=li.xpath('./a[2]/b/text()').extract_first()
if self.page_num<=3:#爬取前三页
new_url=format(self.url%self.page_num)#替换新页面连接
self.page_num+=1
#手动发送请求:callback回调函数是专门用作数据解析
yield scrapy.Request(url=new_url,callback=self.parse)#发送新的连接,同时还是使用parse方法进行解析