简介
Scrapy,Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试.
Scrapy主要包括了以下组件:
- 引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心) - 调度器(Scheduler) -----> 相当于一个队列,且是一个优先队列
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 - 下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛 (Scrapy下载器是建立在 twisted 这个高效的异步模型上的) - 爬虫(Spiders) -----> spider的工作是解析,去网上下东西不是它的活,是下载器的活,spider只需要把下载器传来的responses进行解析。解析的最后只有两个结果:url和item(url给调度器,item给管道)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面 - 项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。 - 下载器中间件(Downloader Middlewares)
位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。 - 爬虫中间件(Spider Middlewares)
介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。 - 调度中间件(Scheduler Middewares)
介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Scrapy运行流程大概如下:
- 引擎从调度器中取出一个链接(URL)用于接下来的抓取
- 引擎把URL封装成一个请求(Request)传给下载器
- 下载器把资源下载下来,并封装成应答包(Response)
- 爬虫解析Response
- 解析出实体(Item),则交给实体管道进行进一步的处理
- 解析出的是链接(URL),则把URL交给调度器等待抓取
安装配置
Linux下的安装(包括mac)
pip install scrapy
Windows下的安装
a. 下载twisted
http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
b. 安装wheel
pip3 install wheel
c. 安装twisted
进入下载目录,执行
pip3 install Twisted‑18.7.0‑cp36‑cp36m‑win_amd64.whl
d. 安装pywin32
pip3 install pywin32
e. 安装scrapy
pip3 install scrapy
基本命令
一,全局命令
1,startproject—创建项目命令
语法: scrapy startproject <project_name>
前置条件: 项目不需要存在
示例: scrapy startproject TestDemo001
进入到相应的文件目录下,打开cmd,输入以上命令,则会在相应的文件目录下建立一个项目
2,shell—创建一个shell环境调试response的命令
语法: scrapy shell
前置条件: 项目不需要存在
示例: scrapy shell “http://www.yetianlian.com/”
打开cmd命令窗口,运行该命令,(安装IPython的情况下默认启动IPython编辑器)
如果不想输出log可以使用这个命令:
scrapy shell --nolog “http://www.yetianlian.com/”
左侧标记部分是scrapy shell 可使用的命令,其中view (查看页面的响应部分,处理含有动态js网址时较实用)和response.xpath(查看xpath路径选取的正确性的)命令经常使用(需注意!)
3,view—查看页面内容
语法: scrapy view
前置条件: 项目不需要存在
示例: scrapy view “http://www.yetianlian.com/”
打开cmd命令窗口,运行该命令,在浏览器中打开指定的URL
4,version—查看版本信息
语法: scrapy version [-v]
前置条件: 项目不需要存在
示例: scrapy version [-v]
打开cmd命令窗口,运行该命令,显示相应scrapy的版本号
此命令可以用来查看scrapy是否安装成功,该命令同时输出Python、Twisted以及平台的信息,方便bug的提交。
5,settings—查看配置文件参数命令
语法: scrapy settings [options]
前置条件: 项目不需要存在
示例: scrapy settings --get BOT_NAME
输出: scrapybot
显示settings中的具体设置的设定值
6,runspider 运行爬虫命令
语法: scrapy runspider <spider_file.py>
前置条件: 项目不需要存在
示例: scrapy runspider ytl.py
未创建项目的情况下,运行一个编写好的spider模块
7,fetch 显示爬取过程
语法: scrapy fetch [url]
前置条件: 项目存在/不存在均可
示例: scrapy fetch “http://www.yetianlian.com/”
命令其实是在标准输出中显示调用一个爬虫来爬取指定的url的全过程。
8,bench 硬件测试命令
语法: scrapy bench
前置条件: 项目不需要存在
测试scrapy在硬件上运行的效率
注:错误解析
这是因为您enum不是标准的库enum模块。您可能已enum34安装该软件包。
检查是否属于这种情况的一种方法是检查财产
解决错误方法:参考
从python 3.6开始,enum34库不再与标准库兼容。该库也是不必要的,因此您只需卸载它即可。我安装的python版本是3.6.5版本的,所有直接卸载enum库即可
卸载 enum34库
pip uninstall enum34
二,项目命令
1,genspider—通过模板生成scrpay爬虫
查看scrapy genspider 的模板
命令: scrapy genspider -l
语法: scrapy genspider [-t template]
前置条件: 项目需要存在
展示basic模板的内容: scrapy genspider -d basic
示例: 创建spider.py,命令:scrapy genspider -t basic ytl yetianlian.com
文件目录图示:
自动生成的代码图示:
由此可见,genspider生成的默认代码与刚doc窗口展示的代码相同,其中命令中代表爬虫的唯一名称代表的是爬虫所允许爬取的域名范围。
2,crawl—启动爬虫命令
语法: scrapy crawl
前置条件: 项目需要存在
示例: scrapy crawl ytl
启动命名为ytl的爬虫(需要cd进入相应的文件目录下,运行该命令)
3,check—检查爬虫完整性
语法: scrapy check [-l]
前置条件: 项目需要存在
示例: scrapy check ytl
检查一些语法、import和warning等错误
4,list—查看爬虫列表命令
语法: scrapy list
前置条件: 项目需要存在
示例: scrapy list
查看一下这个项目里面目前有哪些爬虫
5,edit—编辑爬虫命令
语法:scrapy edit
前置条件:项目需要存在
示例:scrapy edit ytl
用设定的编辑器编辑给定的spider,该命令是提供一个快捷方式,开发者可以自由选择其他工具或者IDE来编写和调试spider。(Linux系统下)
6,parse—获取给定的URL使用相应的spider分析处理
语法: scrapy parse [options]
前置条件: 项目需要存在
示例: scrapy parse “http://www.yetianlian.com/” -c parse
如果提供–callbac选项,可以自动检测spider中的解析方法进行处理。支持以下的选项:
–spider=SPIDER: 没有指定spider的情况下其程序搜索,用这个选项可以强行指定某个spider
–a NAME=VALUE: 用来设定spider需要的参数,可以多个
–callback或-c: 指定spider里面用于处理response的函数,没有强行制定的话,默认使用parse函数
–pipelines: 用来指定后续的pipelines,可以灵活定制
–rules或-r: 通过CrawlSpider设定的规则来选取对应的函数当作解析response的回调函数
–noitems: 不显示抓取的items
–nolinks: 不显示提取的链接
–nocolour: 输出的结果不要高亮显示(不建议用)
–depth或-d: 设置爬取深度,默认为1
–verbose或-v: 显示被爬取的每层的相关信息
7,deploy命令
语法: scrapy deploy [target:project|-l|-L]
目的: 将项目部署到scrapyd服务。
项目结构以及爬虫应用简介
project_name/
scrapy.cfg
project_name/
__init__.py
items.py
middlewares.py
pipelines.py
settings.py
spiders/
__init__.py
爬虫1.py
爬虫2.py
爬虫3.py
文件说明:
-
scrapy.cfg 项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
-
items.py 设置数据存储模板,用于结构化数据,如:Django的Model
-
pipelines 数据处理行为,如:一般结构化的数据持久化
-
settings.py 配置文件,如:递归的层数、并发数,延迟下载等
#项目名称 BOT_NAME = 'DemoScrapy' #爬虫的位置 SPIDER_MODULES = ['DemoScrapy.spiders'] #新建爬虫的位置 NEWSPIDER_MODULE = 'DemoScrapy.spiders' #浏览器信息设置 USER_AGENT = 'DemoScrapy (+http://www.yourdomain.com)' #是否遵守robots.txt规则 ROBOTSTXT_OBEY = True #并发数,默认16 CONCURRENT_REQUESTS = 32 #下载延迟,每个请求等待3秒 DOWNLOAD_DELAY = 3 #启用COOKIES COOKIES_ENABLED = False #默认的请求报头 DEFAULT_REQUEST_HEADERS = {} ...
-
spiders 爬虫目录,如:创建文件,编写爬虫规则
注意:一般创建爬虫文件时,以网站域名命名
windows系统编码错误时:
解决方法:
import sys,io
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
import scrapy
class ChoutiSpider(scrapy.Spider):
name = 'chouti'
allowed_domains = ['chouti.com']
start_urls = ['https://dig.chouti.com/']
def parse(self, response):
pass
创建爬虫
scrapy genspider -t basic ganji "http://anqing.ganji.com/"
执行爬虫
scrapy crawl ganji -o ganji.csv # 想 生 成 csv,就 ganji.csv
scrapy crawl ganji -o ganji.json # 想 生 成 json,就 ganji.json
# 所 以 像 上 面 这 种 格 式 就 不 需 要 管 道 了,可 以 直 接 生 成
备注:可以在settings.py中配置user-agent
上面那个 start_urls 可以写循环,就可以分页爬:
注意:
上面def parse() 这个方法 解析完没有存管道,直接就存 data.txt 里了;
如果用scrapy爬,还是规规矩矩用管道存,SPIDER的目的就是解析,解析完管道pipeline管道存储
def parse() 这个方法有俩属性 self 和 response,如果response返回的是html就用xpath解析,如果返回的 json 串,就用 json库 去解析;解析之后会有返回值(item 和 URL ),如果是 item 就给管道存储,如果是 url 就给调度器入队列;
部分解析出来的存的 item (也有url的话),就先把 item 不给 yield 返回,先把它暂时存起来—— request.meta[ ‘’] 中暂存,剩下的 url 进行函数回调 callback,最后的item再一起 yield,让管道对完整的 item 进行存储
在爬取数据时,可以选择是否往 …/robots.txt/ 发送验证,是否允许爬取,一般设置为False
使用 scrapy 解析文本内容时,可以使用每个应用中的 response.xpath(xxx) 进行数据的解析。
print(response.xpath(...)) 得到的是一个`Selector对象。
selector对象可以继续xpath进行数据的解析。
备注:xpath使用方法:
1.//+标签 表示从全局的子子孙孙中查找标签
2.//+标签 表示从子代中查找标签
3.查找带有xxx属性的标签: 标签+[@标签属性=“值”]
4.查找标签的某个属性: /标签/@属性
5.从当前标签中查找时:.//+标签
因为用 return 的话,循环一次就结束了,其他的循环不进行了;
但是用 yield 的话,就可以一直循环解析,解析很多数据
yield + 循环 —> 生成器;生成数据用的
response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8')
hxs = HtmlXPathSelector(response)
print(hxs) # selector对象
hxs = Selector(response=response).xpath('//a')
print(hxs) #查找所有的a标签
hxs = Selector(response=response).xpath('//a[2]')
print(hxs) #查找某一个具体的a标签 取第三个a标签
hxs = Selector(response=response).xpath('//a[@id]')
print(hxs) #查找所有含有id属性的a标签
hxs = Selector(response=response).xpath('//a[@id="i1"]')
print(hxs) # 查找含有id=“i1”的a标签
# hxs = Selector(response=response).xpath('//a[@href="link.html"][@id="i1"]')
# print(hxs) # 查找含有href=‘xxx’并且id=‘xxx’的a标签
# hxs = Selector(response=response).xpath('//a[contains(@href, "link")]')
# print(hxs) # 查找 href属性值中包含有‘link’的a标签
# hxs = Selector(response=response).xpath('//a[starts-with(@href, "link")]')
# print(hxs) # 查找 href属性值以‘link’开始的a标签
# hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]')
# print(hxs) # 正则匹配的用法 匹配id属性的值为数字的a标签
# hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/text()').extract()
# print(hxs) # 匹配id属性的值为数字的a标签的文本内容
# hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/@href').extract()
# print(hxs) #匹配id属性的值为数字的a标签的href属性值
# hxs = Selector(response=response).xpath('/html/body/ul/li/a/@href').extract()
# print(hxs)
# hxs = Selector(response=response).xpath('//body/ul/li/a/@href').extract_first()
# print(hxs)
# ul_list = Selector(response=response).xpath('//body/ul/li')
# for item in ul_list:
# v = item.xpath('./a/span')
# # 或
# # v = item.xpath('a/span')
# # 或
# # v = item.xpath('*/a/span')
# print(v)
备注:xpath中支持正则的使用: 用法 标签+[re:test(@属性值,“正则表达式”)]
获取标签的文本内容: /text()
获取第一个值需要 selector_obj.extract_first() 获取所有的值 selector_obj.extract() 值在一个 list 中
中间件:
返回的状态码不是200的话,下载器中间件会返给下载器重新爬;
不管是 去 还是 回来,中间件都给你拦一下。所以如果你在 settings 里把下载器中间件给注释掉了,这里不给拦一下的话,就不太好
爬图片:
小爬虫及管道文件
class DemoscrapyItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field()
balance = scrapy.Field()
pass
#生成器
tables = response.xpath("//table[@class='newdatatable kcb_tables']")
for table in tables:
di = DemoscrapyItem()
#extract()返回unicode字符串
name = table.xpath("./tr[2]/td[1]/a/text()").extract()
balance = table.xpath("./tr[2]/td[2]/span/text()").extract()
di["name"] = name[0]
di["balance"] = balance[0]
yield di
# !!!!return 和 yield区别?
# !!!!yield scrapy.Request(url) 返回给 引擎 ,引擎 寻找 调度器
#启用管道
ITEM_PIPELINES = {
'DemoScrapy.pipelines.DemoscrapyPipeline': 300,
}
class DemoscrapyPipeline(object):
def __init__(self):
self.f = open("aaa.txt","w+")
#可选,做参数初始化
def process_item(self, item, spider):
#ensure_ascii=False中文不按照ascii处理
content = json.dumps(dict(item),ensure_ascii=False)
print(content)
self.f.write(content)
#返回给引擎
return item
#def open_spider(self,spider):
# self.f.close()
# 和__init__一样
def close_spider(self,spider):
self.f.close()
当然也可以直接输出文件
scrapy crawl xxx -o xxx.json
scrapy crawl xxx -o xxx.jsonl
scrapy crawl xxx -o xxx.csv
scrapy crawl xxx -o xxx.xml
模拟 scrapy 发送请求,得到response
scrapy shell "http://data.eastmoney.com/zjlx/detail.html"
Spiders
Spider类定义了如何爬取某个网站,包括了爬取的动作以及如何从网页的内容中提取结构化数据。还句话说,Spider就是你定义爬取的动作及分析某个网页的地方。
-
以初始的URL初始化Request,并设置回调函数callback。当该request下载完毕并返回时,将生成response,并作为参数传给该回调函数。start_requests()读取start_urls中的URL,并以parse为回调函数生成Request.
-
在回调函数内分析返回的网页内容,返回Item对象或者Request或者一个包含二者的可迭代容器。返回的Request对象之后回经过Scrapy处理,下载相应的内容,并调用设置的callback函数。
-
在回调函数内,你可以使用选择器来分析网页内容,并根据分析的数据生成item。
-
最后,由spider返回的item将被存到数据库或使用Feed exports存到文件中.
Spider参数
Spider可以通过接收参数来修改其功能。spider参数一般用来定义初始URL或者指定先值爬取网站的部分。在运crawl时添加-a可以传递Spider参数:
scrapy crawl myspider -a category=electronics
Spider在构造器中获取参数。
import scrapy
class MySpider(Spider):
name = 'myspider'
def __init__(self, category=None, *args, **kwargs):
super(MySpider, self).__init__(*args, **kwargs)
self.start_urls = ['http://www.example.com/categories/%s' % category]
# ...
class scrapy.spider.Spider
Spider是最简单的spider。每个器它的spider必须继承自该类。Spider仅仅请求给定的 start_urls/start_requests,并根据返回的结果(resulting/responses)调用spider 的 parse 方法。
name:
定义 spider 名字的字符串。定义了 Scrapy 如何定位 spider,必须是唯一的。不过可以生成多个相同的spider实例。
如果该spider爬取单个网站,一个常见的做法是以该网站来命名spider。
allowed_domains:
可选,包含了spider允许爬取的域名列表。当 offsiteMiddleware 启用时,域名不在列表中的URL不会被跟进。
start_urls:
URL列表,当没有制定特定的URL时,spider将从该列表中来时进行爬取。因此,第一个被获取到的页面的URL将是该列表之一。后续的URL将会从获取到的数据中提取。
start_requests()
该方法必须返回一个 可迭代对象。该对象包含了 spider 用于爬取的第一个Request 。当 spider 启动爬取并且未指定 URL 时,该方法被调用。当指定了 URL时,make_requests_from_url() 将被调用来创建 Request 对象。仅近乎 uibei 调用一次,可视为生成器。该方法默认使用 start_urls 的 url 生成 Reqest 。如果你想修改最初爬取某个网站的 Request 对象,可以重写该方法。
make_requests_from_url(url)
该方法接收一个URL并返回用于爬取的 Request 对象。在初始化时被start_requests() 调用,也用于转化 url 为 request 。默认未被复写的情况下该方法返回的 Request 对象中,parse() 作为回调函数,dont_filter 参数也被设置为开启。
parse(respone)
当 response 没有指定回调函数时,该方法时 Scapy 处理下载的 response 的默认方法。
parse 负责处理 respones 并返回处理的数据以及跟进的 URL。
Spider 对其他的 Request 及Item 的可迭代的对象。
log(message[,level,component]):
使用scrapy.log.msg()方法记录message。log中自动带上该spider的name属性。
closed(reason)
当spider关闭时,该函数被调用。该方法提供了一个提到调用signals.connect()来监听spider_closed信号的快捷方式。
样例:
import scrapy
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = [
'http://www.example.com/1.html',
'http://www.example.com/2.html',
'http://www.example.com/3.html',
]
def parse(self, response):
self.log('A response from %s just arrived!' % response.url)
class scrapy.contrib.spiders.CrawlSpider
爬取一般网站常用的spider。其定义了一些规则来提供跟link的方便机制。除了继承过来的属性外,其提供了一个新的属性。
rules: 一个包含一个或多个Rule对象的集合。每个Rule对爬取网站的动作定义了特定的表现。如果rule匹配了相同的链接,则根据它们在本属性中被定义的顺序,第一个会被使用。
parse_start_url(response) 当start_url的请求返回时,该方法被调用。该方法分析最初的返回值并必须返回一个 Item 对象或者 一个 Request 对象或者 一个可迭代的包含二者对象。
爬取规则(Crawling rules)
class scrapy.contrib.spiders.Rule(link_extractor, callback=None,
cb_kwargs=None, follow=None, process_links=None, process_request=None)
link_extractor
是一个Link Extractor对象,其定义了如何从爬取到的页面提取链接。
callback
是一个callable或string。从link_extractor中每获取到链接时将调用该函数。该回调函数接受一个response作为其第一个参数,并返回一个包含Itm以及Request对象的列表。
cb_kwargs:
包含传递给回调函数的参数的字典。
follow
是一个布尔值,指定了根据该规则从response提取的链接是否需要跟进。如果callback为None,follow默认设置为True,否则默认为False。
process_link
是一个callable或string。主要用来过滤。
process_reqeust
是一个 callable 或 string 。该规则提取到每个 request 时都会调用该函数。该函数必须返回一个 request 或 None.
import scrapy
from scrapy.contrib.spiders import CrawlSpider,Rule
from scrapy.contrib.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name='example.com'
# 允许爬取链接的域名
allowed_domains=['example.com']
start_urls=['http://www.example.com']
rules=(
# # 提取匹配 'category.php' (但不匹配 'subsection.php') 的链接并跟进链接(没有callback意味着follow默认为True)
Rule(LinkExtractor(allow=('category\.php'),deny=('subsection\.php'))),
# # 提取匹配 'item.php' 的链接并使用spider的parse_item方法进行分析
Rule(LinkExtractor(allow=('item\.php'),callback='parse_item')),
)
def parse_item(self,response):
self.log('Hi, this is an item page! %s' % response.url)
item=scrapy.Item()
item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
return item
该spider将从example.com的首页开始爬取,获取category以及item的链接并对后者使用parse_item方法。当item获得返回时,将使用XPath处理HTML并生成一些数据填入Item中。
class scrapy.contrib.spiders.XMLFeedSpider
XMLFeedSpider 被设计用于通过迭代各个节点来分析XML源。迭代其可以从iternodes,xml,html选择。鉴于xml以及html迭代器需要先读取所有DOM再分析而引起的性能问题,一般还是推荐使用iternodes。
你必须定义下列类属性来设置迭代器以及标签名:
iterator: 用于确定使用哪个迭代器,
-
iternodes-一个高性能的基于正则表达式的迭代器;
-
html’-使用selector的迭代器。使用DOM进行分析,其需要将所有的DOM载入内存,当数据量大的时候会产生问题。
-
xml- 和 html 一样。
默认值为iternodes。itertag 一个包含开始迭代的节点名的sring。 namespaces一个由(prefix,url)元组所组成的list。 其定义了在该文档中会被 spider处理的可用的namespace。 prefix及url会被自动调用redister_namespace()生成namespace。 可以通过itertag属性中制定节点的namespace。 class YourSpider(XMLFeedSpider): namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')] itertag = 'n:url' # ...
adapt_response(response)
该方法在spider分析response前被调用。可以在response被分析前使用该函数来修改内容。
parse_node(response, selector)
当节点符合提供的标签名时itertag该方法被调用。接收到的response以及相应的Selector作为参数传递给该方法。该方法返回一个Item对象或者Request对象或者一个包含二者的可迭代对象。
process_results(response, results)
当spider返回结果时该方法被调用。设定该方法的目的是在结果返回给框架核心之前作最后的处理。
from scrapy import log
from scrapy.contrib.spiders import XMLFeedSpider
from myproject.items import TestItem
class MySpider(XMLFeedSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.xml']
iterator = 'iternodes' # This is actually unnecessary, since it's the default value
itertag = 'item'
def parse_node(self, response, node):
log.msg('Hi, this is a <%s> node!: %s' % (self.itertag, ''.join(node.extract())))
item = TestItem()
item['id'] = node.xpath('@id').extract()
item['name'] = node.xpath('name').extract()
item['description'] = node.xpath('description').extract()
return item
class scrapy.contrib.spiders.CSVFeedSpider
用来爬取CSV文件网页,该spider除了其按行遍历而不是节点之外其他和XMLFeedSpider十分类似,而每次迭代时调用的是parse_row().
delimiter 在 CSV文件 中用于区分字段的分隔符。类型为 string。默认为’,’;
headers 在 CSV文件 中包含的用来提取字段的行的列表。
parse_row(response,row)
该方法接收一个 response 对象及一个以提供或检测出来的 header 为键的字典。该spider中,你可以覆盖 adapt_response 及 process_results 方法来进行预处理及后处理。
from scrapy import log
from scrapy.contrib.spiders import CSVFeedSpider
from myproject.items import TestItem
class MySpider(CSVFeedSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.csv']
delimiter = ';'
headers = ['id', 'name', 'description']
def parse_row(self, response, row):
log.msg('Hi, this is a row!: %r' % row)
item = TestItem()
item['id'] = row['id']
item['name'] = row['name']
item['description'] = row['description']
class scrapy.contrib.spiders.SitemapSpider
SitemapSpider 使你爬取网站时可以通过Sitemaps来发现爬取的 URL。其支持嵌套的sitemap,并能从robots.txt会获取sitemap的url。
sitemap_urls 包含你要爬取的url的sitemap的url列表。也可以指定为一个robots.txt,spider会从中分析并提取url。
sitemap_rules一个包含(regex,callback)元组的列表。
regex 是一个用于匹配从sitemap提供的url的正则表达式;可以是一个字符串或者编译的正则对象。
callback 指定了匹配正则表达式的url的处理函数。可以是一个字符串或者callale。规则按顺序进行匹配,之后第一个匹配才会被应用。如果忽略该属性,sitemap中发现的所有url将会被parse函数处理。
sitemap_follow 一个用于匹配要跟进的sitemap的正则表达式的列表。
其仅仅被应用在使用 Sitemap index files 来指向其他 sitemap 文件的站点。
默认情况下所有的sitemap都回被跟进。
sitemap_alternate_links 指定当一个 url 又可选的链接时,是否跟进。有些非英文网站会在一个 url 块内提供其他语言的网站链接。
<url>
<loc>http://example.com/</loc>
<xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/>
</url>
当 sitemap_alternate_links 设置时,两个URL都会被获取。
当 sitemap_alternate_links 关闭时,只有 http://example.com/ 会被获取。默认 sitemap_alternate_links 关闭。
样例
使用parse处理通过sitemap发现的所有url:
from scrapy.contrib.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
def parse(self, response):
pass # ... scrape item here ...
用特定的函数处理某些url,其他的使用另外的 callback:
from scrapy.contrib.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
sitemap_rules = [
('/product/', 'parse_product'),
('/category/', 'parse_category'),
]
def parse_product(self, response):
pass # ... scrape product ...
def parse_category(self, response):
pass # ... scrape category ...
跟进 robots.txt 文件定义的sitemap并只跟进包含有 …sitemap_shop 的 url:
from scrapy.contrib.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/robots.txt']
sitemap_rules = [
('/shop/', 'parse_shop'),
]
sitemap_follow = ['/sitemap_shops']
def parse_shop(self, response):
pass # ... scrape shop here ...
在 SitemapSpider 中使用其他 url:
from scrapy.contrib.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/robots.txt']
sitemap_rules = [
('/shop/', 'parse_shop'),
]
other_urls = ['http://www.example.com/about']
def start_requests(self):
requests = list(super(MySpider, self).start_requests())
requests += [scrapy.Request(x, self.parse_other) for x in self.other_urls]
return requests
def parse_shop(self, response):
pass # ... scrape shop here ...
def parse_other(self, response):
pass # ... scrape other here ...
获取响应数据中的cookie
cookie是什么?它相当于一个容器,是存储缓存数据的,像咱们第一次访问数据的时候,加载的特别慢,第二次就比较快了,因为第一次加载到的数据已经存储在cookie里面了;
还有像咱们网购加购物车,加到哪里去了?就是加到 cookie 里去了,你把浏览器关了,再登陆上去,购物车的信息依然有,cookie有一个生命周期,到了时间之后,你所存的数据就没了,cookie加载的是缓存数据
返回的 cookie 在哪呢?在 response 里
返回的response中,无法通过 .cookies 获取cookie,只能通过从响应头中获取,但是获取的结果还得需要解析。
{b'Server': [b'Tengine'], b'Content-Type': [b'text/html; charset=UTF-8'], b'Date': [
b'Fri, 20 Jul 2018 13:43:42 GMT'], b'Cache-Control': [b'private'], b'Content-Language': [b'en'],
b'Set-Cookie': [b'gpsd=5b05bcae8c6f4a273a53addfc8bbff22; domain=chouti.com; path=/; expires=Sun,
19-Aug-2018 13:43:42 GMT', b'JSESSIONID=aaadbrXmU-Jh2_kvbaysw; path=/'], b'Vary': [b'Accept-Encoding'],
b'Via': [b'cache15.l2nu29-1[69,0], kunlun9.cn125[73,0]'], b'Timing-Allow-Origin': [b'*'],
b'Eagleid': [b'6a78b50915320942226762320e']}
所以,要通过 scrapy封装 的方法,将cookie解析出来
import scrapy
from scrapy.http.cookies import CookieJar
class ChoutiSpider(scrapy.Spider):
name = 'chouti'
allowed_domains = ['chouti.com']
start_urls = ['https://dig.chouti.com/']
cookie_dict = {}
def parse(self, response):
cookie_jar = CookieJar()
cookie_jar.extract_cookies(response,response.request) # 从里面获取cookie信息,需要传俩参数,一个 response,一个 request
for k, v in cookie_jar._cookies.items(): # cookie里面存储的东西都是键值对存储的,键 是 字符串,值 也是字符串;往cookie里存的时候是键值对,取的时候也是键值对往外取;
# 把cookie 里面的信息一条一条往外取,取完之后又开始循环
for i, j in v.items(): # 值 在存储的时候 也是键值对,i , j
for m, n in j.items():
self.cookie_dict[m] = n.value
print(self.cookie_dict)
备注:CookieJar中封装的内容特别丰富,print(cookie_jar._cookies) 包含很多
{'.chouti.com': {'/': {'gpsd': Cookie(version=0, name='gpsd', value='fcb9b9da7aaede0176d2a88cde8b6adb',
port=None, port_specified=False, domain='.chouti.com', domain_specified=True, domain_initial_dot=False,
path='/', path_specified=True, secure=False, expires=1534688487, discard=False, comment=None,
comment_url=None, rest={}, rfc2109=False)}}, 'dig.chouti.com': {'/': {'JSESSIONID':
Cookie(version=0, name='JSESSIONID', value='aaa4GWMivXwJf6ygMaysw', port=None, port_specified=False,
domain='dig.chouti.com', domain_specified=False, domain_initial_dot=False, path='/',
path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={},
rfc2109=False)}}}
自动登录抽屉并点赞和取消赞代码示例
import scrapy
from scrapy.http.response.html import HtmlResponse
# import sys,os,io
# sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
from ..items import XzxItem
from scrapy.http import Request
from scrapy.http.cookies import CookieJar
class ChoutiSpider(scrapy.Spider):
name = 'chouti'
allowed_domains = ['chouti.com']
start_urls = ['https://dig.chouti.com/r/ask/hot/1']
cookie_dict = {}
def start_requests(self):
for url in self.start_urls:
yield Request(url=url,callback=self.parse)
def parse(self, response):
# 1. 去 第 一 次 访 问 页 面 中 获 取 cookie
# print(response.headers['Set-Cookie'],type(response.headers['Set-Cookie']))
cookie_jar = CookieJar() # 空
cookie_jar.extract_cookies(response, response.request) # cookie_jar 中 包 含 了 cookie
for k, v in cookie_jar._cookies.items():
for i, j in v.items():
for m, n in j.items():
self.cookie_dict[m] = n.value
# 2. 向 https://dig.chouti.com/login 发 送 POST 请 求
yield Request(
url='https://dig.chouti.com/login',
method='POST',
body="phone=8615901492719&password=qwer1234&oneMonth=1",
cookies=self.cookie_dict, # 请 求 时 带 cookie 信 息 ,模 拟 浏 览 器 访 问
headers={
'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
'content-type':'application/x-www-form-urlencoded; charset=UTF-8',
},
callback=self.check_login
)
def check_login(self,response):
print(response.text)
yield Request(url='https://dig.chouti.com/',callback=self.index)
def index(self,response):
news_id_list = response.xpath('//div[@id="content-list"]//div[@class="part2"]/@share-linkid').extract()
for news_id in news_id_list:
# 赞
"""
news_url = "https://dig.chouti.com/link/vote?linksId=%s" %(news_id,)
yield Request(
url=news_url,
method='POST',
cookies=self.cookie_dict,
callback=self.output
)
"""
# 取消赞
news_url = "https://dig.chouti.com/vote/cancel/vote.do"
yield Request(
url=news_url,
method='POST',
body="linksId=%s" %(news_id,),
cookies=self.cookie_dict,
headers={
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
callback=self.output
)
# 2.1 获取页面
page_list = response.xpath('//*[@id="dig_lcpage"]//a/@href').extract()
for url in page_list:
url = "https://dig.chouti.com" + url
yield Request(url=url,callback=self.index)
def output(self,response):
print(response.text)
备注:
爬取过程中的坑:
请求头中,一定要携带 content-type 参数。请求过程中的 url 不能重复,尤其是和 起始url。
我们可以使用 urllib 中的 urlencode 帮我们把数据转化为 formdata 格式的
from urllib.parse import urlencode
ret = {'name':'xxx','age':18}
print(urlencode(ret))
items
爬取的主要目的就是从非结构性的数据源提取结构性数据。Scrapy 提供 Item类 来满足这样的需求。Item对象是种简单的容器,保存了爬取到的数据。
其提供了类似于字典的 API 以及用于声明可用字段的简单语法。
声明Item
class Product(scrapy.Item):
name=scrapy.Field()
price=scrapy.Field()
stock=scrapy.Field()
last_updated=scrapy.Field(serializer=str)
Item字段
Field 对象指明了每个字段的元数据。你可以为每个字段指明任何类型的元数据。设置Field对象的主要目的就是在一个地方定义好所有的元数据。
与Item配合
创建item
product = Product(name='Desktop PC', price=1000)
print product
Product(name='Desktop PC', price=1000)
获取字段值
> > > product['name']
> > > Desktop PC
> > > product.get('name')
> > > Desktop PC
> > > product['price']
> > > 1000
> > > product['last_updated']
> > > Traceback (most recent call last):
> > > ...
> > > KeyError: 'last_updated'
> > > product.get('last_updated', 'not set')
> > > not set
> > > product['lala'] # getting unknown field
> > > Traceback (most recent call last):
> > > ...
> > > KeyError: 'lala'
> > > product.get('lala', 'unknown field')
> > > 'unknown field'
> > > 'name' in product # is name field populated?
> > > True
> > > 'last_updated' in product # is last_updated populated?
> > > False
> > > 'last_updated' in product.fields # is last_updated a declared field?
> > > True
> > > 'lala' in product.fields # is lala a declared field?
> > > False
设置字段值
> > > product['last_updated'] = 'today'
> > > product['last_updated']
> > > today
> > > product['lala'] = 'test' # setting unknown field
> > > Traceback (most recent call last):
> > > ...
> > > KeyError: 'Product does not support field: lala'
获取所有获取到的值
print product.keys()
# ['price', 'name']
print product.items()
# [('price', 1000), ('name', 'Desktop PC')]
复制item
product2=Product(product)
print product2
{'name': 'Desktop PC', 'price': 1000}
product3=product2.copy()
print product3
{'name': 'Desktop PC', 'price': 1000}
根据item创建字典
print dict(product)
# {'price': 1000, 'name': 'Desktop PC'}
根据字典创建item
print Product({'name': 'Laptop PC', 'price': 1500})
{'name': 'Laptop PC', 'price': 1500}
扩展Item
可以通过继承原始的Item来扩展item
class DiscountedProduct(Product):
discount_percent = scrapy.Field(serializer=str)
discount_expiration_date = scrapy.Field()
也可以通过使用原字段的元数据,添加新的值或修改原来的值来扩展字段元数据。
class SpecificProduct(Product):
name = scrapy.Field(Product.fields['name'], serializer=my_serializer)
Item对象
class scrapy.item.Item([arg])
返回一个根据给定参数可选初始化的item,Item读值了标准的dict API。包括初始化函数也相同,为以额外添加的属性是:field,一个包含了所有声明的字段的字典,而不仅仅是货渠道的字段。
实战学习
附录:命令行工具
创建项目
scrapy startproject myproject
该命令经会在myproject目录中创建一个Scrapy项目。进入到项目根目录,就可以使用scrapy命令来管理和控制你的项目了。
控制项目
有些Scrapy命令要求在Scrapy项目中运行。另外注意,有些命令在项目里运行时的效果有些区别。
scrapy -h
查看所有可用命令
scrapy -h
Scrapy提供了两种类型的命令。一种是必须在scrapy项目中运行,另一种则不与需要。
全局命令:
项目命令 |命令|语法|功能| |-|-| |crawl|scrapy crawl
|使用spider进行爬取| |check|scrapy check [-l]
|运行contract检查| |list|scrapy list|列出当前项目种所有可用的spider| |edit|scrapy edit
|使用EDITOR种设定的编辑器给定的spider| |parse|scrapy parse [options]
|获取给定的URL并使用响应的spider分析处理。| |genspider|scrapy genspider [-t template]
|仅仅是创建spider的一种快捷方法。该方法可以用提前定义好的模板来生成spider,也可以自己创建spider的源码文件| |deploy|scrapy deploy [ | -l | -L ]
|将项目部署到Scrapyd服务| |bench|scrapy bench
|运行benchmark测试|
parse命令参数选项
–spider=SPIDER: 跳过自动检测spider并强制使用特定的spider;
–a NAME=VALUE: 设置spider的参数
–callback-cspider 中用于解析返回的回调函数
–pipelines: 在pipeline中处理item
–rules-r: 使用CrawlSpider规则来发现用来解析返回的回调函数
–noitems 不显示爬取道的item
–nolinks 不显示爬取到的链接
–nocolour 避免使用pygments对输出着色
–depth-d 指定跟进链接诶请求的层数默认为1
附录:命令行工具
创建项目
scrapy startproject myproject
该命令经会在myproject目录中创建一个Scrapy项目。进入到项目根目录,就可以使用scrapy命令来管理和控制你的项目了。
控制项目
有些Scrapy命令要求在Scrapy项目中运行。另外注意,有些命令在项目里运行时的效果有些区别。
scrapy -h
查看所有可用命令
scrapy -h
Scrapy提供了两种类型的命令。一种是必须在scrapy项目中运行,另一种则不与需要。
全局命令:
项目命令 |命令|语法|功能| |-|-| |crawl|scrapy crawl
|使用spider进行爬取| |check|scrapy check [-l]
|运行contract检查| |list|scrapy list|列出当前项目种所有可用的spider| |edit|scrapy edit
|使用EDITOR种设定的编辑器给定的spider| |parse|scrapy parse [options]
|获取给定的URL并使用响应的spider分析处理。| |genspider|scrapy genspider [-t template]
|仅仅是创建spider的一种快捷方法。该方法可以用提前定义好的模板来生成spider,也可以自己创建spider的源码文件| |deploy|scrapy deploy [ | -l | -L ]
|将项目部署到Scrapyd服务| |bench|scrapy bench
|运行benchmark测试|
parse命令参数选项
–spider=SPIDER: 跳过自动检测spider并强制使用特定的spider;
–a NAME=VALUE: 设置spider的参数
–callback-cspider 中用于解析返回的回调函数
–pipelines: 在pipeline中处理item
–rules-r: 使用CrawlSpider规则来发现用来解析返回的回调函数
–noitems 不显示爬取道的item
–nolinks 不显示爬取到的链接
–nocolour 避免使用pygments对输出着色
–depth-d 指定跟进链接诶请求的层数默认为1
–verbose-v 显示每个请求的详细信息