小组爬虫项目一步一步按计划进行,翻译也提上了日程,先看一遍Scrapy 1.5的官方文档热热身先 :)
翻译文档到时会统一整合到gitbook上!这里只分享一些自己用Scrapy的练习~
~实践是检验真理的唯一标准~
Novel
单线程
先写个简单的没有JS异步加载的爬小说吧!
关键点在于用选择器来定位你要爬的内容。。单单在这一点上,我就觉得Scrapy比BeautifulSoup方便不知道多少倍了。。因为Scrapy的shell简直是神器,手动测试=>精准定位=>直接复制=>bingo!
chapter = response.xpath("//div[@class='bookname']/h1/text()").extract()
content = response.xpath("//div[@id='content']/text()").extract()
next_page = response.xpath("//div[@class='bottem']/a/@href")[3].extract()
至于如何储存提取的数据,那就根据需求来选择就好了。可以选择python的写入文件,也可以存储在json、jl里面。
但是,这种单线程爬取,实在是太慢了。。。对于上千上万的请求。。怕是要挂一晚上哦~~
所以,继续阅读文档,再学一波多线程爬取。。那就perfect啦!(后来发现Scrapy就是协程的,所以。。如果是多核电脑,你只可以开多个进程来加快速度~)
多线程
暂时如果不跳出Scrapy的限制的话,可能只能同时启动多个spider来实现效率上的提升吧?
爬图片
题库
最近做了个爬题库的项目,根据金主爸爸的需求去爬,统一格式。。方才明白,爬虫容易,洗数据,统一格式才是。。让人抓狂的。
不过一件事情翻炒翻炒,就会有不同的收获!
储存方式
pipeline
scrapy默认 -o 参数是储存到一个文件里面,但是如果数据动不动上G,打开都容易死机~更别说处理的时候了(而且面对大数据,现成工具的用处并不大。。。千万不要尝试手动处理。。。自己写脚本才是比较geek的方法!!)
这次储存的是json格式的数据,而且金主爸爸要求每个文件大小不超过20M。
一开始我是一整套爬下来,再用脚本分片,感觉效率还是不够高(关键是还很麻烦)。
后来再查了点资料,翻炒了文档,就去模仿自定义了一个pipeline
# 自定义pipeline
import json
import codecs
import math
import os
point = {}
class ChuZhongPipeline(object):
# 初始化时指定要操作的文件
def __init__(self):
print('\nstoring..ChuZhong..')
# 存储数据,将 Item 实例作为 json 数据写入到文件中
def process_item(self, item, spider):
global point
pointName = item['pointName']
grade = item['grade']
dir_path = "./地理/%s" % grade
# 不存在文件夹则创建
if not os.path.exists(dir_path):
os.makedirs(dir_path)
# 不存在该知识点的文件名,则创建
if pointName not in point:
point[pointName] = 1
count = str(math.floor(point[pointName] / 1000))
filename = dir_path + "/" + pointName + "_" + count + ".json"
self.file = codecs.open(filename, 'w', encoding='utf-8')
self.file.close()
elif pointName in point:
point[pointName]+= 1
# 写入文件
if (point[pointName] % 1000) != 0:
count = str(math.floor(point[pointName] / 1000))
currentname = dir_path + "/" + pointName + "_" + count + ".json"
self.file = codecs.open(currentname,'a', encoding='utf-8')
lines = json.dumps(dict(item), ensure_ascii=False) + ',\n'
self.file.write(lines)
self.file.close()
elif (point[pointName] % 1000) == 0:
count = str(math.floor(point[pointName] / 1000))
currentname = dir_path + "/" + pointName + "_" + count + ".json"
self.file = codecs.open(currentname,'a', encoding='utf-8')
lines = json.dumps(dict(item), ensure_ascii=False) + ',\n'
self.file.write(lines)
self.file.close()
count = str(math.floor(point[pointName] / 1000))
filename = dir_path + "/" + pointName + "_" + count + ".json"
self.file = codecs.open(filename, 'w', encoding='utf-8')
return item
这里实现的功能是,根据传过来的item,创建相应文件夹,和文件名,并限制一个文件的行数(scrapy写入json是一行一个数据),代码比较累赘,但是。。起码完成了我的需求是吧:)
这个代码一开始是遇到了坑的:
- Process_item()里面只能用item里面的数据来自动化,如果你直接在这个pipeline里面设置一个全局变量,试图通过它来完成自动化创建与写入文件,我感觉是行不通的~~(关键是我太菜了!)
- 由于scrapy的协程机制,写入文件的时候,你必须重新打开相应文件,来写入。。不然就会把一些奇奇怪怪的数据写到一个与他无关的文件里面,数据就混乱了!!
- 还有一点就是,注意代码逻辑!!!注意代码逻辑!!!注意代码逻辑!!!
爬取数据任务完成了之后,只需要再写个脚本,把格式统一一下,把json文件的两个 [] 补上,就大功告成了!
item exporter
这是用另一种方式储存json,但是这样储存是把所有数据写到一行里,不利于后续处理,所以我舍弃了。
from scrapy.exporters import JsonItemExporter
class ExercisesPipeline(object):
# 调用 scrapy 提供的 json exporter 导出 json 文件
def __init__(self):
self.file = open('questions_exporter.json', 'wb')
# 初始化 exporter 实例,执行输出的文件和编码
self.exporter = JsonItemExporter(self.file,encoding='utf-8',ensure_ascii=False)
# 开启倒数
self.exporter.start_exporting()
def close_spider(self, spider):
self.exporter.finish_exporting()
self.file.close()
# 将 Item 实例导出到 json 文件
def process_item(self, item, spider):
self.exporter.export_item(item)
return item
Settings设置的高级应用
官方文档中scrapy中settings参数有四个级别:
- 命令行选项(Command line Options)(最高优先级)
- 项目设定模块(Project settings module)
- 命令默认设定模块(Default settings per-command)
- 全局默认设定(Default global settings) (最低优先级)
在这个项目中,我主要用了第二个setting。众所周知,一个scrapy项目中只有一个全局settings.py,所以我一般只会在里面设置一些全局参数(UA等一系列可以不变的变量),然后就可以用custom_settings来自定义项目的一些设置:
Eg:
spiders/somespider.py
from ..custom_settings import *
class Spider1(CrawlSpider):
name = "spider1"
custom_settings = custom_settings_for_spider1
pass
class Spider2(CrawlSpider):
name = "spider2"
custom_settings = custom_settings_for_spider2
custom_settings_for_spider1 = {
'LOG_FILE': 'spider1.log', # 日志文件
'CONCURRENT_REQUESTS': 100, # 请求并发数
'DOWNLOADER_MIDDLEWARES': { # 下载中间件
'spider.middleware_for_spider1.Middleware': 667,
},
'ITEM_PIPELINES': {
'spider.mysql_pipeline_for_spider1.Pipeline': 400, # 管道中间件
},
}
custom_settings_for_spider2 = {
'LOG_FILE': 'spider2.log',
'CONCURRENT_REQUESTS': 40,
'DOWNLOADER_MIDDLEWARES': {
'spider.middleware_for_spider2.Middleware': 667,
},
'ITEM_PIPELINES': {
'spider.mysql_pipeline_for_spider2.Pipeline': 400,
},
}
这里的设置优先级会比settings高,就可以为某一特定spider写一个定制的下载中间件,来绕过一下反爬机制
如果每个爬虫的储存方式,有不同需求,就可以定制不同的pipeline来储存,只需要把你的想法加到pipeline.py即可(记住对应名字)