4. Spider的使用
在Scrapy中,要抓取网站的链接配置、抓取逻辑、解析逻辑里其实都是在Spider中配置的。
Spider要做的事就是有两件:
定义抓取网站的动作
和分析爬取下来的网页
。
4.1 Spider运行流程:
整个抓取循环过程如下所述:
- 以初始的URL初始化Request,并设置回调函数。请求成功时Response生成并作为参数传给该回调函数。
- 在回调函数内分析返回的网页内容。返回结果两种形式,一种为字典或Item数据对象;另一种是解析到下一个链接。
- 如果返回的是字典或Item对象,我们可以将结果存入文件,也可以使用Pipeline处理并保存。
- 如果返回Request,Response会被传递给Request中定义的回调函数参数,即再次使用选择器来分析生成数据Item。
4.2 Spider类分析:
- Spider类源代码:打开文件
Python36/Lib/site-packages/scrapy/spiders/__init__.py
import logging
import warnings
from scrapy import signals
from scrapy.http import Request
from scrapy.utils.trackref import object_ref
from scrapy.utils.url import url_is_from_spider
from scrapy.utils.deprecate import create_deprecated_class
from scrapy.exceptions import ScrapyDeprecationWarning
from scrapy.utils.deprecate import method_is_overridden
#所有爬虫的基类,自定义的爬虫必须从继承此类
class Spider(object_ref):
#定义spider名字的字符串(string)。spider的名字定义了Scrapy如何定位(并初始化)spider,所以其必须是唯一的。
#name是spider最重要的属性,而且是必须的。
#一般做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 douban.com ,该spider通常会被命名为 douban
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=log.DEBUG, **kw):
log.msg(message, spider=self, level=level, **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):
assert not hasattr(self, '_crawler'), "Spider already bounded to %s" % 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)
#@property
#def crawler(self):
# assert hasattr(self, '_crawler'), "Spider not bounded to any crawler"
# return self._crawler
#@property
#def settings(self):
# return self.crawler.settings
#该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response
#该方法仅调用一次
def start_requests(self):
for url in self.start_urls:
yield self.make_requests_from_url(url)
#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
@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__
Spider
类继承自scrapy.spiders.Spider
.Spider
类这个提供了start_requests()
方法的默认实现,读取并请求start_urls
属性,并调用parse()
方法解析结果。Spider
类的属性和方法:name
:爬虫名称,必须唯一的,可以生成多个相同的Spider实例,数量没有限制。allowed_domains
: 允许爬取的域名,是可选配置,不在此范围的链接不会被跟进爬取。start_urls
: 它是起始URL列表,当我们没有实现start_requests()方法时,默认会从这个列表开始抓取。custom_settings
: 它是一个字典,专属于Spider的配置,此设置会覆盖项目全局的设置,必须定义成类变量。crawler
:它是由from_crawler()方法设置的,Crawler对象包含了很多项目组件,可以获取settings等配置信息。settings
: 利用它我们可以直接获取项目的全局设置变量。start_requests()
: 使用start_urls里面的URL来构造Request,而且Request是GET请求方法。parse()
: 当Response没有指定回调函数时,该方法会默认被调用。closed()
: 当Spider关闭时,该方法会调用。
4.3 实战案例:
- 任务:使用scrapy爬取关键字为
python
信息的百度文库搜索信息(每页10条信息) - url地址分析:
https://wenku.baidu.com/search?word=python&pn=0
第一页 url地址分析:
https://wenku.baidu.com/search?word=python&pn=10
第二页具体实现:
- ① 使用scrapy命令创建爬虫项目dbwenku
$ scrapy startproject bdwenku
- ② 创建spider爬虫文件wenku:
$ cd bdwenku
$ scrapy genspider wenku wenku.baidu.com
- ③ 编辑
wenku.py
爬虫文件,代码如下:
# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']
def parse(self, response):
print("Hello Scrapy")
print(response)
- ④ 运行测试:
$ scrapy crawl wenku
- 爬取数据出现错误:
DEBUG: Forbidden by robots.txt:
请求被拒绝了 - 也就是百度文库的robots.txt设置了禁止外部爬取信息。
- 解决办法:在settings.py配置文件中,将ROBOTSTXT_OBEY的值设为False,忽略robot协议,继续爬取。
...
2018-05-11 11:00:54 [scrapy.core.engine] INFO: Spider opened
2018-05-11 11:00:54 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-05-11 11:00:54 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6027
2018-05-11 11:00:56 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://wenku.baidu.com/robots.txt> (referer: None)
2018-05-11 11:00:56 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://wenku.baidu.com/search?word=python&pn=0>
2018-05-11 11:00:56 [scrapy.core.engine] INFO: Closing spider (finished)
2018-05-11 11:00:56 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 1,
'downloader/exception_type_count/scrapy.exceptions.IgnoreRequest': 1,
...
- ⑤ Spider爬虫文件:
wenku.py
的几种写法:
# 单请求的信息爬取
# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']
def parse(self, response):
dllist = response.selector.xpath("//dl")
#print(len(dllist))
for dd in dllist:
print(dd.xpath("./dt/p/a/@title").extract_first())
# 多个请求的信息爬取
# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0','https://wenku.baidu.com/search?word=python&pn=10','https://wenku.baidu.com/search?word=python&pn=20']
def parse(self, response):
dllist = response.selector.xpath("//dl")
#print(len(dllist))
for dd in dllist:
print(dd.xpath("./dt/p/a/@title").extract_first())
print("="*70) #输出一条每个请求后分割线
# 实现一个爬取循环,获取10页信息
# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']
p=0
def parse(self, response):
dllist = response.selector.xpath("//dl")
#print(len(dllist))
for dd in dllist:
print(dd.xpath("./dt/p/a/@title").extract_first())
print("="*70)
self.p += 1
if self.p < 10:
next_url = 'https://wenku.baidu.com/search?word=python&pn='+str(self.p*10)
url = response.urljoin(next_url) #构建绝对url地址(这里可省略)
yield scrapy.Request(url=url,callback=self.parse)
python
python教程
PYTHON测试题
简明_Python_教程
如何自学 Python(干货合集)
python
python
Python介绍(Introduction to Python)
Python编程入门(适合于零基础朋友)
Python教程
======================================================================
python
十分钟学会Python
Python 基础语法(一)
python基础分享
Python入门
python新手教程
python资源中文大全
python_笔记
Python与中文处理
python
======================================================================
python
python教程
PYTHON测试题
简明_Python_教程
如何自学 Python(干货合集)
python
python
Python介绍(Introduction to Python)
Python编程入门(适合于零基础朋友)
Python教程
======================================================================