简介
采用异步框架。
scrapy中文文档:
https://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/overview.html#
优点:
使用框架可以自己完成log ,并发,存储,监控。
整体架构图:
Scrapy Engine: 这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等
Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。
Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理,
Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),
Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)
Downloader Middlewares(下载中间件):可以当作是一个可以自定义扩展下载功能的组件
Spider Middlewares(Spider中间件):可以理解为是一个可以自定扩展和操作引擎和Spiders中间‘通信‘的功能组件(比如进入Spiders的Responses;和从Spiders出去的Requests)
数据流向:
1.引擎从调度器中取出一个链接(URL)用于接下来的抓取
2.引擎把URL封装成一个请求(Request)传给下载器
3.下载器把资源下载下来,并封装成应答包(Response)
4.爬虫解析Response
5.解析出实体(Item),则交给实体管道进行进一步的处理
6.解析出的是链接(URL),则把URL交给调度器等待抓取
新建工程
scrapy startproject projectName
这个命令会在当前目录下创建一个新目录projectName,此目录的结构如下:
│ scrapy.cfg
│
└─projectName
│ items.py
│ pipelines.py
│ settings.py
│ __init__.py
│
└─spiders
__init__.py
scrapy.cfg: 项目配置文件
projectName/: 项目python模块, 呆会代码将从这里导入
projectName/items.py: 项目items文件
projectName/pipelines.py: 项目管道文件
projectName/settings.py: 项目配置文件
projectName/spiders: 放置spider的目录
定义Item
类名必须含有Item。
Items是将要装载抓取的数据的容器,在其中定义我们需要的数据。
例如:
class MyspiderItem(scrapy.Item):
define the fields for your item here like:
name = scrapy.Field()
每一项都是一个list,存储json时需使用dict函数转换item,有时也需要根据需求对item进行额外的转换然后存入json文件中。
json.dumps(dict(item), ensure_ascii=False)
新建爬虫
scrapy genspider [-t template] <name> <domain>
在当前文件夹或当前项目的spiders文件夹中创建一个新的爬虫。该<name>参数设置为爬虫的name(不能与project重复),而<domain>用于生成allowed_domains和start_urls爬虫的属性。
在spiders目录下生成文件:
<name>.py
运行爬虫
命令行方式:
scrapy crawl <name>
IDE方式,需要在项目的根目录(与scrapy.cfg同级)创建一个入口脚本:
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', '<name>'])
这样能在IDE中运行调试。
编写注意
新建的爬虫文件中,会自动含有以下三个成员:
name = '<name>' #即新建爬虫的名字,在整个项目中有且只能有一个、名字不可重复
allowed_domains = ['<name>.com']
#这个不是必须的;但是在某写情况下需要用得到,
#比如使用爬取规则的时候就需要了;
#它的作用是只会跟进存在于allowed_domains中的URL。
#不存在的URL会被忽略。
start_urls = [' '] #初始爬虫页面,根据需要进行调整
一般我们需要对url进行拼接,然后调用Request进行并发处理:
def start_requests(self):
for i in range(1, 10):
url = self.start_urls[0] + str(i) + self.bashurl
yield Request(url, self.parse) #scrapy的优点,并发处理多个url
生成的文件中会自包含一个类,这个类一般用作Request的回调函数,不要轻易改为他用:
def parse(self, response):
scrapy使用的是自带的Selector配合XPath解析获取需要的数据,但也可以使用BeautifulSoup解析(相比Selector慢)。
XPath语法:
http://www.w3school.com.cn/xpath/xpath_syntax.asp
示例:
for content_url in Selector(text=index_html).xpath('//div[@class="list post-list"]')
导入item类时,需要加入路径,否则会一直提示找不到包,如下:
fpath = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
ffpath = os.path.abspath(os.path.join(fpath,".."))
sys.path.append(ffpath)
from mySpider.items import MyspiderItem
item的传递
yield Request(str(item["source_url"][0]), meta={'item': item}, callback=self.get_url_content)
有时item在一个页面无法获取完整,需要在另一个页面继续获取,这时需要就要通过上面的方法传递。
通过下面的方式取得传递的item:
def get_url_content(self, response):
item = response.meta['item']
#return item # ××× 有时会需要保存多中item,处理完一个item,这时不能直接return,否则程序就结束了
#yield item # √√√ 正确方式
#yield Request(url, meta={'item': item, 'rdt': rdt, 'comments':cmt,
# 'rewards':rewards,'total': total, 'curpage': cur}, callback=self.processNextItem)
# 传递多个参数
区分不同的item
if isinstance(item, MyspiderItem):
...
else:
...
配置
在对应的setting文件中,可以进行一些配置。
设置pipeline,需要在pipelines.py文件中编写对应函数进行处理,可以编写存储为json文件,也可以编写MySql存储(需进行登录等配置)。
设置:
ITEM_PIPELINES = {
'mySpider.pipelines.MyspiderPipeline': 300,
}
mySpider(项目目录).pipelines.MyspiderPipeline(其中定义的类) 后面的 300是优先级程度(1-1000随意设置,数值越低,组件的优先级越高)。
可根据需求定义多个类(存储为不同方式),设定不同的优先级。
其中pipelines自己生成的类中有一个函数process_item,其参数item和spider是不可改变的,主要也是对item进行处理:
def process_item(self, item, spider):
···
return item # 这个返回值是必须的
注意存储文件时,可以事先打开一个文件,设定utf-8编码,因为Request得到的数据是unicode编码的。
def __init__(self):
self.file = codecs.open('smzdm.json', 'w+', encoding='utf-8')
注意文件的默认保存目录是项目的根目录(与scrapy.cfg同级)
去重
有时会爬到重复的数据,需要进行去重后存放到文件或者数据库中。
通常做法可以在pipelines中加入去重类,然后在settings中调高其优先级便可以达到此效果,但是有一个缺点是这种方法是在获取完所有数据后才去重。
示例:
url_count = 0
class DuplicatesPipesline(object):
def __init__(self):
self.urls_seen = {}
if os.path.exists("smzdm_dup.json"):
self.urls_seen = json.loads(io.open("smzdm_dup.json", 'r', encoding='utf-8').read())
#打开之前存储的去重数据文件
def process_item(self, item, spider):
global url_count
url_count += 1
if item["source_url"][0] in self.urls_seen:
raise DropItem("Duplicate item found") #丢弃掉重复数据
else:
self.urls_seen[item["source_url"][0]] = {}
if url_count % 300 == 0:
io.open("smzdm_dup.json", "w", encoding="utf-8").write(
json.dumps(self.urls_seen, indent=4, sort_keys=True, ensure_ascii=False))
return item
def __del__(self):
if url_count:
io.open("smzdm_dup.json", "w", encoding="utf-8").write(
json.dumps(self.urls_seen, indent=4, sort_keys=True, ensure_ascii=False))
# 最后将数据写入
配置优先级:
ITEM_PIPELINES = {
'mySpider.pipelines.MyspiderPipeline': 300,
'mySpider.pipelines.DuplicatesPipesline': 200,
}
网友评论