Scrapy 框架(四)之Spider

Spider

Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。

class scrapy.Spider是最基本的类,所有编写的爬虫必须继承这个类。

主要用到的函数及调用顺序为:

__init__() : 初始化爬虫名字和start_urls列表

start_requests() 调用make_requests_from url():生成Requests对象交给Scrapy下载并返回response

parse() : 解析response,并返回Item或Requests(需指定回调函数)。Item传给Item pipline持久化 , 而Requests交由Scrapy下载,并由指定的回调函数处理(默认parse()),一直进行循环,直到处理完所有的数据为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 所有爬虫的基类,用户定义的爬虫必须从这个类继承
class Spider(object_ref):
# 定义spider名字的字符串(string)。spider的名字定义了Scrapy如何定位(并初始化)spider,所以其必须是唯一的。
# name是spider最重要的属性,而且是必须的。
# 一般做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite
name = None
custom_settings = None

# 初始化,提取爬虫名字,start_ruls
def __init__(self, name=None, **kwargs):
if name is not None:
self.name = name
elif not getattr(self, 'name', None):
# 如果爬虫没有名字,中断后续操作则报错
raise ValueError("%s must have a name" % type(self).__name__)
# python 对象或类型通过内置成员__dict__来存储成员信息
self.__dict__.update(kwargs)
# URL列表。当没有指定的URL时,spider将从该列表中开始进行爬取。 因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。
if not hasattr(self, 'start_urls'):
self.start_urls = []

@property
def logger(self):
logger = logging.getLogger(self.name)
return logging.LoggerAdapter(logger, {'spider': self})

# 打印Scrapy执行后的log信息
def log(self, message, level=logging.DEBUG, **kw):
self.logger.log(level, message, **kw)

@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = cls(*args, **kwargs)
spider._set_crawler(crawler)
return spider

# 判断对象object的属性是否存在,不存在做断言处理
def set_crawler(self, crawler):
warnings.warn("set_crawler is deprecated, instantiate and bound the "
"spider to this crawler with from_crawler method "
"instead.",
category=ScrapyDeprecationWarning, stacklevel=2)
assert not hasattr(self, 'crawler'), "Spider already bounded to a " \
"crawler"
self._set_crawler(crawler)

def _set_crawler(self, crawler):
self.crawler = crawler
self.settings = crawler.settings
crawler.signals.connect(self.close, signals.spider_closed)

# 该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response
# 该方法仅调用一次
def start_requests(self):
cls = self.__class__
if method_is_overridden(cls, Spider, 'make_requests_from_url'):
warnings.warn(
"Spider.make_requests_from_url method is deprecated; it "
"won't be called in future Scrapy releases. Please "
"override Spider.start_requests method instead (see %s.%s)." % (
cls.__module__, cls.__name__
),
)
for url in self.start_urls:
yield self.make_requests_from_url(url)
else:
for url in self.start_urls:
yield Request(url, dont_filter=True)

# start_requests()中调用,实际生成Request的函数。
# Request对象默认的回调函数为parse(),提交的方式为get
def make_requests_from_url(self, url):
return Request(url, dont_filter=True)

# 默认的Request对象回调函数,处理返回的response。
# 生成Item或者Request对象。用户必须实现这个类
def parse(self, response):
raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))

@classmethod
def update_settings(cls, settings):
settings.setdict(cls.custom_settings or {}, priority='spider')

@classmethod
def handles_request(cls, request):
return url_is_from_spider(request.url, cls)

@staticmethod
def close(spider, reason):
closed = getattr(spider, 'closed', None)
if callable(closed):
return closed(reason)

def __str__(self):
return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))

__repr__ = __str__

主要属性和方法

  • name:爬虫名称,是定义 Spider 名字的字符串,Spider的名字定义了Scrapy如何定位(并初始化)Spider,所以其必须是唯一的。
  • allowed_domains:允许爬取的域名,是可选配置,不在此范围的链接不会被跟进爬取。
  • start_urls:是起始URL列表,当我们没有实现start_requests()方法时,默认会从这个列表开始抓取。
  • custom_settings:是一个字典,是专属于本 Spider 的配置,此设置会覆盖项目全局的设置。此设置必须在初始化前被更新,必须定义成类变量。
  • crawler:它是由 from_crawler()方法设置的,代表的是本 Spider 类对应的 Crawler 对象。Crawler对象包含了很多项目组件,利用它我们可以获取项目的一些配置信息,如最常见的获取项目的设置信息,即Settings
  • settings:一个Settings对象,利用它我们可以直接获取项目的全局设置变量。除了基础属性,Spider还有一些常用的方法。
  • start_requests():此方法用于生成初始请求,它必须返回一个可迭代对象。此方法会默认使用start_urls里面的URL来构造Request,而且Request是GET请求方式。如果我们想在启动时以POST方式访问某个站点,可以直接重写这个方法,发送POST请求时使用FormRequest
  • parse():当请求url返回网页没有指定回调函数时,默认的Request对象回调函数。用来处理网页返回的Response,处理返回结果,并从中提取出想要的数据和下一步的请求,然后返回。该方法需要返回一个包含Request或Item的可迭代对象。
  • closed():当Spider关闭时,该方法会被调用,在这里一般会定义释放资源的一些操作或其他收尾操作。

实例:腾讯招聘网自动翻页采集

  • 创建一个新的爬虫:
1
scrapy genspider tencent 'tencent.com'
  • 编写items.py
1
2
3
4
5
6
7
class TencentItem(scrapy.Item):
name = scrapy.Field()
detailLink = scrapy.Field()
positionInfo = scrapy.Field()
peopleNumber = scrapy.Field()
workLocation = scrapy.Field()
publishTime = scrapy.Field()
  • 编写tencent.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import re

import scrapy
from scrapy import Selector

from mySpider.items import TencentItem


class TencentSpider(scrapy.Spider):
name = 'tencent'
allowed_domains = ['hr.tencent.com']
start_urls = [
'http://hr.tencent.com/position.php?&start=0#a'
]

def parse(self, response):
res = Selector(response)
for each in res.xpath('//*[@class="even"]'):
item = MyspiderItem()
name = each.xpath('./td[1]/a/text()').extract_first()
detailLink = each.xpath('./td[1]/a/@href').extract_first()
positionInfo = each.xpath('./td[2]/text()').extract_first()
peopleNumber = each.xpath('./td[3]/text()').extract_first()
workLocation = each.xpath('./td[4]/text()').extract_first()
publishTime = each.xpath('./td[5]/text()').extract_first()

item['name'] = name
item['detailLink'] = detailLink
item['positionInfo'] = positionInfo
item['peopleNumber'] = peopleNumber
item['workLocation'] = workLocation
item['publishTime'] = publishTime

curpage = re.search('(\d+)', response.url).group(1)
page = int(curpage) + 10
url = re.sub('\d+', str(page), response.url)

# 发送新的url请求加入待爬队列,并调用回调函数 self.parse
yield scrapy.Request(url, callback=self.parse)

# 将获取的数据交给pipeline
yield item
  • 编写pipeline.py文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json


class TencentJsonPipeline(object):

def __init__(self):
self.file = open('tencent.json', 'w', encoding='utf-8')

def process_item(self, item, spider):
content = json.dumps(dict(item), ensure_ascii=False) + "\n"
self.file.write(content)
return item

def close_spider(self, spider):
self.file.close()
  • 在 setting.py 里设置ITEM_PIPELINES
1
2
3
ITEM_PIPELINES = {
'mySpider.pipelines.TencentJsonPipeline': 300,
}

小结

parse()方法的工作机制:

1
2
3
4
5
6
7
8
9
1. 因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型;
2. 如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息;
3. scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取;
4. 取尽第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline里处理;
5. parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse)
6. Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路);
7. 取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
8. 程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items;
7. 这一切的一切,Scrapy引擎和调度器将负责到底。
-------------本文结束感谢您的阅读-------------

本文标题:Scrapy 框架(四)之Spider

文章作者:GavinLiu

发布时间:2018年05月05日 - 22:05

最后更新:2018年05月05日 - 22:05

原始链接:http://gavinliu4011.github.io/post/1cfe1288.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

请博主吃个鸡腿吧
0%