创建一个Scrapy项目
1.创建项目文件夹tutorial
scrapy startproject tutorial
2.创建Spider类
Spider的用法
使用命令行创建一个Spider
cd tutorial
scrapy genspider quotes quotes.toscrape.com
第一个参数是Spider的名称,第二个参数是网站域名
执行完毕后,spider文件夹中多了一个quotes.py。
- name 项目的名字,用于区分不同的Spider
- allowed_domains,允许爬取的域名
- start_urls,它包含了Spider在启动时爬取的url列表,初始请求是由它来定义的。
- parse,Spider的一个方法,负责解析下载start_urls里面的连接构成的请求后返回的相应、提取数据或者进一步生成要处理的请求。
# -*- coding: utf-8 -*-
import scrapy
from scrapyspider.items import QuoteItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
item = QuoteItem()
item['item'] = quote.css('.text::text').extract_first()
item['author'] = quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags::text').extract()
yield item
next = response.css('.pager .next a::attr(href)').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url,callback=self.parse)
3.创建Item
Item是保存爬取数据的容器。
类似于字典+额外的保护机制,避免拼写错误或者定义字段错误。
创建时需要继承scrapy.Item类,并且定义类型为scrapy.Field的字段。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class QuoteItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
4.解析Response&使用Item
在parse()方法中,直接对response变量包含的内容进行解析,解析**text、author、tags。
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
item = QuoteItem()
item['item'] = quote.css('.text::text').extract_first()
item['author'] = quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags::text').extract()
yield item
选择器selector:CSS或XPath
Scrapy提供了两个实用的快捷方法
response.xpath()和response.css()
二者功能完全等同于
response.selector.xpath()和response.selector.css()
CSS
这里使用CSS选择器,给出CSS选择器的基础使用规则。
选择器 | 例子 | 含义 |
---|---|---|
.class | .aaa | 选择class="aaa"的所有元素 |
#id | #firstname | 选择 id="firstname" 的所有元素 |
* | 选择所有元素 | |
element | p | 选择所有<p>元素 |
element element | div p | 选择 <div> 元素内部的所有 <p> 元素 |
a[title] | 选取所有拥有title属性的a元素 |
对于text:
extract_first()方法获取结果的第一个元素,可以避免空列表取[0]导致数组越界。extract_first('Default Image')匹配不到结果时返回该参数Default Image
extract()方法获取所有结果组成的列表
XPath
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性/属性获取 |
[ ] | 增加限制条件 |
text() | 文本获取 |
属性多值匹配-某些节点的某个属性可能有多个值。
使用contains()函数,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就可以完成匹配了。
例如代码:
from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li")]/a/text()')
print(result)
//运行结果
['first item']
多属性匹配-根据多个属性确定一个节点
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li") and @name="item"]/a/text()')
print(result)
//运行结果
['first item']
正则匹配
response对象不能直接调用re()和re_first()方法。如果想要对全文进行正则匹配,可以先调用xpath()方法再正则匹配。
5.后续Request
上面操作实现从初始页面抓取内容。
- 我们需要从当前页面中找到信息来生成下一个请求
- 在下一个请求的页面里找到信息再构造再下一个请求。
- 这样循环往复迭代,从而实现整站的爬取。
基于按钮的链接:http://quotes.toscrape.com/page/2
利用scrapy.Request构造下一个请求
scrapy.Request两个参数
- url 请求链接
- callback 回调函数。当指定了该回调函数的请求完成后,获取到响应,引擎会将该响应作为参数传递给这个回调函数。回调函数进行解析或生成下一个请求,回调函数如上文的
parse()所示。
由于每页的结构相同,可以再次使用parse()方法来做页面解析。
next = response.css('.pager .next a::attr(href)').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url,callback=self.parse)
- 获取a超链接中的href属性,这里用到了::attr(href)操作。然后再调用extract_first()方法获取内容。
- urljoin()方法,将相对URL构造成一个绝对的URL(补全URL)
- 利用scrapy.Request构造新请求
6.运行
scrapy crawl quotes
这里运行的时候有各种可能的原因导致不能从目标获取内容
- 网址的错误
- 格式错误如缩进问题
- 网页有反爬虫,
- 在settings文件中添加USER_AGENT
- 添加IP代理
- 改机器人协议以及cookie
- 设置延迟
- 分段函数中所要爬取的url有反爬虫,可以添加print(requests.get(url).status_code)调试
7.保存到文件
Scrapy提供的Feed Exports可以轻松抓取结果输出。
#json
scrapy crawl quotes -o quotes.json
#jsonline 每一个Item输出一行JSON
scrapy crawl quotes -o quotes.jl
scrapy crawl quotes -o quotes.csv
scrapy crawl quotes -o quotes.xml
scrapy crawl quotes -o quotes.pickle
scrapy crawl quotes -o quotes.marshal
#ftp 远程输出
scrapy crawl quotes -o ftp://user::pass@ftp.example.com/path/to/quotes.csv
8.使用Item Pipeline(项目管道)
调用发生在Spider产生Item之后。
当Spider解析完Response之后,Item就会传递到Item Pipeline,被定义的Item Pipeline组件会顺次调用,完成一连串的处理过程,比如数据清洗、存储等。
主要功能有4个:
- 清理HTML数据
- 验证爬取数据,检查爬取字段。
- 查重并丢弃重复内容。
- 将爬取结果保存到数据库
将结果保存到MongoDB或者筛选Item。
核心方法
- 必须要实现的一个方法是:process_item(item,spider)
被定义的Item Pipeline会默认调用这个方法对Item进行处理。
返回:Item类型的值(此Item会被低优先级的Item Pipeline的process_item()方法处理,直到所有的方法被调用完毕)或者抛出一个DropItem异常(丢弃该Item,不再进行处理)。 - 实用方法
open_spider(self,spider)
在Spider开启的时候被自动调用,可以在这里做一些初始化操作,如开启数据库连接等。 - 实用方法
close_spider(spider)
在Spider关闭时自动调用,可以在这里做一些收尾工作,如关闭数据库等。 - 实用方法
from_crawler(cls,crawler)
是一个类方法,用@classmethod标识,是一种依赖注入的方式。可以通过crawler对象拿到Scrapy的所有核心组件。
参数:cls就是Class
返回:Class实例
9.Downloader Middleware的用法
下载中间件,是处于Scrapy的Request和Response之间的处理模块。
- 在Scheduler调度出队列的Request发送给Downloader下载之前,也就是可以在Request执行下载之前对其进行修改。
- 在下载后生成的Response发送给Spider之前,可以在生成Response被Spider解析之前对其进行修改。
优先级问题
数字越小越靠近Scrapy引擎,数字越大越靠近Downloader,数字小的Downloader Middleware会被优先调用。
返回类型不同,产生的效果也不同。
- None。Scrapy继续处理该Request,按照设置的优先级顺序依次执行其他Downloader Middleware的process_request()方法对Request进行修改,一直到Downloader把Request执行后得到Response才结束。
- Response对象。不再继续调用更低优先级的Downloader Middleware的process_request()和process_exception()方法,每个Downloader Middleware的process_response()方法转而被依次调用。调用完毕之后,直接将Response对象发送给Spider来处理。
- Request对象。停止执行更低优先级的Downloader Middleware的process_request()方法。将此Request重新放到调度队列里,相当于一个全新的Request,等待被调度。
- IgnoreRequest异常抛出。所有的Downloader Middleware的process_exception()方法会依次执行。若没有一个方法处理这个异常,则Request的errorback()方法就会回调。如果该异常还没有被处理,那么忽略。
核心方法有三个,只需要实现至少一个方法,就可以定义一个Downloader Middleware
-
process_request(request, spider)
Request被Scrapy引擎调度给Downloader之前,该方法会被调用。也就是在Request从队列里调度出来到Downloader下载执行之前,我们都可以用process_request()方法对Request进行处理。
返回:None/Response/Request/抛出IgnoreRequest异常。 -
process_response(request, response, spider)
收到Response后,在Scrapy引擎将Response发送给Spider进行解析之前,可以用该方法对Response进行处理。
返回:Request对象/Response对象/抛出IgnoreRequest异常。 -
process_exception(request, exception, spider)
当Downloader或process_request()方法抛出异常时该方法被调用。
返回:None/Response对象/Request对象。
修改请求时的User-Agent的两种方式:
- 只需要在setting.py中添加一行USER_AGENT的定义即可。
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
- 设置随机的User-Agent,需要借助Downloader Middleware的process_request()方法。
在middlewares.py里面添加一个RandomUserAgentMiddleware的类,如下所示:
import random
class RandomUserAgentMiddleware():
def __init__(self):
self.user_agents = [ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52"
]
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(self.user_agents)
要使之生效,我们需要去调用这个Downloader Middleware。在settings.py中将DOWNLOADER_MIDDLEWARES取消注释并设置成如下内容:
DOWNLOADER_MIDDLEWARES = { 'scrapydownloadertest.middlewares.RandomUserAgentMiddleware': 543,
}
注意:scrapydownloadertest是你项目的文件夹名称,记得修改哦。
10. Spider Middleware的用法
scrapy提供了内置的基础Spider Middleware。如果想要扩展其功能,只需要实现某几个方法即可。
每个Spider Middleware都定义了以下一个或多个方法的类,只需要实现其中一个方法就可以定义一个Spider Middleware,核心方法有如下4个:
-
process_spider_input(response,spider)
当Response被Spider Middleware处理时,该方法被调用。
参数:response,即被处理的Response;spider,即该Response对应的Spider。
返回:None(继续处理Response,调用其他的Spider Middleware,指导Spider处理该Response)或抛出一个异常(调用Request的errback()方法,errback的输出重新输入到中间件中,使用process_spider_output()方法来处理,当其抛出异常时则调用process_spider_exception()来处理) -
process_spider_output(response,result,spider)
当Spider处理Response返回结果时,process_spider_output()方法被调用。
返回:包含Request或Item对象的可迭代对象。 -
process_spider_exception(response,exception,spider)
当Spider或Spider Middleware的process_spider_input()方法抛出异常时,该方法被调用。
返回:None(Scrapy继续调用其他Spider Middleware中的process_spider_exception()方法处理该异常,直到所有Spider Middleware都被调用);可迭代对象(其他Spider Middleware的process_spider_output()方法被调用) -
process_start_requests(start_requests,spider)
以Spider启动的Request为参数被调用,执行过程类似于process_spider_output(),但没有相关联的Response,并且必须返回Request。
返回:必须返回一个包含Request对象的可迭代对象。
可迭代对象:Python中经常使用for来对某个对象进行遍历,此时被遍历的这个对象就是可迭代对象,像常见的list,tuple都是。如果给一个准确的定义的话,就是只要它定义了可以返回一个迭代器的__ iter __ 方法,或者定义了可以支持下标索引的__ getitem __方法(这些双下划线方法会在其他章节中全面解释),那么它就是一个可迭代对象。