网站:
https://tech.china.com/articles/
创建项目:
scrapy startproject scrapyuniversal
之前创建项目,都用scrapy genspider +爬虫名字+域名的方式,此次要创建CrawlSpider需要使用crawl,创建命令:
scrapy genspider -t crawl china tech.china.com
在项目开始之前,要先来了解一下LinkExtractor,
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
allow是一个正则表达式或者列表,定义了从当前页面提取的链接哪些是符合要求的。
callback回调函数,每次从link_extractor中获取到链接时,被调用。接收一个response,返回一个包含Item或者Request对象的列表。
注意:callback中避免属于parse()作为回调函数
follow指定根据该规则提取的链接是否需要跟进,如果callback参数为None,follow默认 为True。否则为False。
定义Rule:
Spider会根据每一个Rule来提取这个页面内的超链接,生成Request.
查看源代码:
可以发现所有的信息都在这个节点内,用正则表达式将文章链接都匹配出来赋值给allow参数。
rules = (
Rule(LinkExtractor(allow='article\/.*\.html',restrict_xpaths='//div[@id="left_side"]//div[@class="con_item"]'),
callback='parse_item',
follow=True),
)
然后找下一页的链接:
Rule(LinkExtractor(restrict_xpaths='//div[@id="pageStyle"]//a[contains(.,"下一页")]'
解析页面:
先定义字段:
from scrapy import Field, Item
class NewsItem(Item):
title = Field()
text = Field()
datetime = Field()
source = Field()
url = Field()
#站点名称,区分不同的站点
website = Field()
获取数据:
def parse_item(self, response):
item = NewsItem()
item['title'] = response.xpath('//h1[@id="chan_newsTitle"]/text()').extract_first()
item['url'] = response.url
item['text'] = ''.join(response.xpath('//div[@id="chan_newsDetail"]//text()').extract()).strip()
item['datetime'] = response.xpath('//div[@id="chan_newsInfo"]/text()').re_first('(\d+-\d+-\d+\s\d+:\d+:\d+)')
item['source'] = response.xpath('//div[@id="chan_newsInfo"]/text()').re_first('来源:(.*)').strip()
item['website'] = '中华网'
yield item
运行之后获取的结果如下:
用Item Loader,通过
add_xpath()
add_value()
add_css()
实现配置化提取。
def parse_item(self, response):
loader = ChinaLoader(item=NewsItem(), response=response)
loader.add_xpath('title', '//h1[@id="chan_newsTitle"]/text()')
loader.add_value('url', response.url)
loader.add_xpath('text', '//div[@id="chan_newsDetail"]//text()')
loader.add_xpath('datetime', '//div[@id="chan_newsInfo"]/text()', re='(\d+-\d+-\d+\s\d+:\d+:\d+)')
loader.add_xpath('source', '//div[@id="chan_newsInfo"]/text()', re='来源:(.*)')
loader.add_value('website', '中华网')
yield loader.load_item()
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, Join, Compose
定义类:
class NewsLoader(ItemLoader):
#定义TakeFirst(),相当于extract_first()方法
default_output_processor = TakeFirst()
class ChinaLoader(NewsLoader):
#Compose两个参数
# Join()也是一个Processor,可以把列表拼合成一个字符串
# lambda可以将头尾空白符去掉
text_out = Compose(Join(), lambda s: s.strip())
source_out = Compose(Join(), lambda s: s.strip())
通用配置的抽取
scrapy genspider -t crawl universal universal
新建一个spider,将上文写的Spider内的属性抽取出来配置成一个JSON,放到config.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": "china"
}
这样的话,要启动爬虫,仅仅需要从配置文件中读取后然后加载到Spider中即可,读取方法代码如下:
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())
此时,我们只需要传入JSON配置文件 的名称,就可以获取配置信息,入口文件代码如下:
from scrapy.crawler import CrawlerProcess
def run():
# Sys.argv[ ]其实就是一个列表,里边的项为用户输入的参数,关键就是要明白这参数是从程序外部输入的,而非代码本身的什么地方。
name = sys.argv[1]
custom_settings = get_config(name)
# 爬虫使用的spider名称
spider = custom_settings.get('spider', 'universal')
project_settings = get_project_settings()
settings = dict(project_settings.copy())
# 将获取到的settings配置和项目全局的settings配置做合并
settings.update(custom_settings.get('settings'))
process = CrawlerProcess(settings)
# 启动
process.crawl(spider, **{'name': name})
process.start()
if __name__ == '__main__':
run()
解析数据的通用配置,代码如下:
# -*- coding: utf-8 -*- from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from scrapyuniversal.items import * from scrapyuniversal.loaders import * from scrapyuniversal.utils import get_config from scrapyuniversal import urls from scrapyuniversal.rules import rules class UniversalSpider(CrawlSpider): name = 'universal' def __init__(self, name, *args, **kwargs): config = get_config(name) self.config = config self.rules = rules.get(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': # eval() 输出是输入的类型 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) def parse_item(self, response): item = self.config.get('item') if item: cls = eval(item.get('class'))() loader = eval(item.get('loader'))(cls, response=response) # 动态获取属性配置 for key, value in item.get('attrs').items(): for extractor in value: if extractor.get('method') == 'xpath': loader.add_xpath(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'css': loader.add_css(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'value': loader.add_value(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'attr': loader.add_value(key, getattr(response, *extractor.get('args'))) yield loader.load_item()
python run.py china 运行。
值得一提的是,引入问题还有运行问题。
引入的时候,会出现一些引入错误,这个需要了解相对引入和绝对引入的一些知识。
在运行的时候,使用命令行运行,会出现一系列的细节问题,不过还好,查查资料都可以解决。