Scrapy通用爬虫个人理解就是针对一系列相似的站点建立一个爬虫框架,包含基本的框架代码,不同点可能在于各个站点的数据形式、爬取规则、页面解析形式。将爬取各个站点所需要的代码分开保存,爬取时再在框架中导入即可。
1. 配置文件
- 配置文件内容:简单点就是针对要爬取的站点所需要的独有的信息,参数都应该写进去。可以包括该爬虫的信息,起始链接和域名,爬虫设置(settings),爬取规则(Rule),以及后面parse_item中需要导入的Item
的组成部件。 - 文件形式:用json形式保存,内部以字典的形式编写,后面就可以用字典的get()函数获取需要的配置。
- 内容编写:正常的配置信息就是变量名作为键,相对应的值作为键值写入就行。难点在于框架中需要对导入的配置文件进行判断、拼接以及需要传入的函数参数,这其中涉及的键值对就需要全局考虑了。
贴上案例文件 xxx.json:
{
"spider": "universal",
"website": "中华网科技",
"type": "新闻",
"index": "http://tech.china.com/",
"settings": {
"USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"
},
"start_urls": {
"type": "dynamic",
"method": "china",
"args": [
5, 10
]
},
"allowed_domains": [
"tech.china.com"
],
"rules": "(Rule(LinkExtractor(allow='article\/.*\\.html', restrict_xpaths='//div[@id=\"left_side\"]//div[@class=\"con_item\"]'),callback='parse_item'),Rule(LinkExtractor(restrict_xpaths='//div[@id=\"pageStyle\"]//a[contains(., \"下一页\")]')))",
"item": {
"class": "NewsItem",
"attrs": {
"title": [
{
"method": "xpath",
"args": [
"//h1[@id='chan_newsTitle']/text()"
]
},
{
"method": "extract_first"
}
],
"url": [
{
"method": "get_attr",
"args": [
"url"
],
"type": "wrap"
}
],
"text": [
{
"method": "xpath",
"args": [
"//div[@id='chan_newsDetail']//text()"
]
},
{
"method": "extract"
},
{
"method": "list2str",
"type": "wrap"
},
{
"method": "strip"
}
],
"datetime": [
{
"method": "xpath",
"args": [
"//div[@id='chan_newsInfo']/text()"
]
},
{
"method": "re_first",
"args": [
"(\\d+\\-\\d+\\-\\d+\\s\\d+\\:\\d+\\:\\d+)"
]
}
],
"source": [
{
"method": "xpath",
"args": [
"//div[@id='chan_newsInfo']/text()"
]
},
{
"method": "re_first",
"args": [
"来源:(.*)"
]
},
{
"method": "strip"
}
],
"website": [
{
"method": "set_value",
"args": [
"中华网"
],
"type": "wrap"
}
]
}
}
}
ps:这配置文件太难了,没点功力根本搞不定。。。。
2. 配置加载
配置文件的获取:正常的 json 文件的加载操作。
from os.path import realpath,dirname
import json
def get_config(name):
path = dirname(realpath(__file__))+ '/configs/' +name + '.json'
with open(path,'r',encoding='utf-8') as f:
return json.loads(f.read())
上面代码配置文件和代码文件不在同一个文件夹下,所以用到了文件路径的获取和拼接。
- 获取配置文件内容:通过上面的get_config()获取配置,然后通过操作字典的方式将配置提取出来,进行下一步的赋值,判断和拼接等操作。
- 提取配置时需要注意的地方(掉过的坑):
- 注意需要赋值的变量类型,提取出来的配置基本都是字符串形式
,对于有些变量的类型可能是函数、字典、列表等,需要经过eval()函数处理才有用。如:
def __init__(self, name, *args, **kwargs):
config = get_config(name)
self.config = config
self.rules = eval(config.get('rules'))
start_urls = config.get('start_urls')
if start_urls:
if start_urls.get('type') == 'static':
self.start_urls = start_urls.get('value')
elif start_urls.get('type') == 'dynamic':
self.start_urls = list(eval('urls.' + start_urls.get('method'))(*start_urls.get('args', [])))
self.allowed_domains = config.get('allowed_domains')
super(UniversalSpider, self).__init__(*args, **kwargs)
- Item的生成,每个站点爬取的信息可能不同,用到的数据形式,解析规则也会不同,所以Item的生成完全需要从配置文件中获取。
ps:Item的拼接太难了,个人觉得可以先写正常爬取时的parse_item()函数,再一点点的拆解下来,防止漏掉某部分参数或属性。个人觉得可以先写正常爬取时的parse_item()函数,再一点点的拆解下来,防止漏掉某部分参数或属性。
贴上代码:
def parse_item(self, response):
# 获取item配置
item = self.config.get('item')
if item:
data = eval(item.get('class') + '()')
# 动态获取属性配置
for key, value in item.get('attrs').items():
data[key] = response
for process in value:
type = process.get('type', 'chain')
if type == 'chain':
# 动态调用函数和属性
if process.get('method'):
data[key] = getattr(data[key], process.get('method'))(*process.get('args', []))
elif type == 'wrap':
args = [data[key]] + process.get('args', [])
data[key] = eval(process.get('method'))(*args)
yield data
参考文档:《Python3 网络爬虫开发实战》 13.10章