1.什么是Scrapy?
Scrapy是一个应用程序框架,用于对网站进行爬行和提取结构化数据,这些结构化数据可用于各种有用的应用程序,如数据挖掘、信息处理或历史存档。
尽管Scrapy最初是为 web 抓取 它还可以用于使用API提取数据(例如 Amazon Associates Web Services )或者作为一个通用的网络爬虫。
1.1 简单案例
安装 scrapy
pip install scrapy
1.2 爬取码云开源项目及描述
代码实现
import scrapy
class giteeSpider(scrapy.Spider):
name = 'gitee'
start_urls = [
'https://gitee.com/explore/develop-tools',
]
def parse(self, response):
# 使用 css 选择器 div 元素 class = item
for item in response.css('div.divided.items.explore-repo__list div.item'):
try:
yield {
'title': item.css('a.title.project-namespace-path::text').get(),
'desc': item.css('div.project-desc::text').get(),
}
except TypeError:
print(TypeError)
a_list = response.css('#git-discover-page a')
next_page = a_list[len(a_list)-1].css('::attr(href)').get()
if next_page is not None:
# response.follow,url 传参可以使用如下方法,它会自动解析出地址
# 1.可以使用相对地址
# 2.可以使用选择器
# 3.可以使用标签
yield response.follow(next_page, self.parse)
运行命令 : scrapy runspider giteeSpider.py -o 'project.json' -s FEED_EXPORT_ENCODING=UTF-8
-o 指定输出文件。
-s 指定输出编码,防止出现乱码。
###### 1.2.2 实现效果
1.2.3 代码解析
当运行命令 scrapy runspider giteeSpider.py -o 'project.json' -s FEED_EXPORT_ENCODING=UTF-8
scrapy 会向定义的 start_urls 中的地址发出请求,并默认回调到 parse 方法中,将响应参数 response 作为参数注入,然后使用 css 选择器 解析 开源项目的 标题 和 描述,然后找到下一页,并调用他的另一个方法 response.follow ,parse 作为回调。
1.2.4 技术要点分析
1.css 选择器
2.response.follow 使用
# response.follow,url 传参可以使用如下方法,它会自动解析出地址
# 1.可以使用相对地址
# 2.可以使用选择器
# 3.可以使用标签
2.第一个Scrapy 项目
##### 2.1新建一个项目
scrapy startproject giteeSpiderProject
得到一个项目,结构如下
2.2 新建一个爬虫
根据指引,新建一个爬虫程序,并指定目标网站。
爬虫程序搭建完成。
设置打印 返回的响应结果
2.3 运行爬虫
运行爬虫 scrapy crawl giteeSpider ,此处giteeSpider 为 爬虫名称
打印日志 太长截取一部分。
2.4 使用 CSS 选择器爬取
css 选择器本身会被 引擎转换成 xpath 选择器。
# 使用 css 选择器 div 元素 class = item
for item in response.css('div.divided.items.explore-repo__list div.item'):
try:
yield {
'title': item.css('a.title.project-namespace-path::text').get(),
'desc': item.css('div.project-desc::text').get(),
}
except TypeError:
print(TypeError)
a_list = response.css('#git-discover-page a')
next_page = a_list[len(a_list)-1].css('::attr(href)').get()
if next_page is not None:
# response.follow,url 传参可以使用如下方法,它会自动解析出地址
# 1.可以使用相对地址
# 2.可以使用选择器
# 3.可以使用标签
yield response.follow(next_page, self.parse)
爬取数据效果
2.5 Feed exports_数据输出到 json 文件
使用 Feed exports 把数据写出到指定文件,修改执行命令为
scrapy crawl giteeSpider -O project.json -s FEED_EXPORT_ENCODING=UTF-8
这个 -O
命令行开关覆盖任何现有文件;使用 -o
而是将新内容附加到任何现有文件中。但是追加到JSON文件会使文件内容无效JSON。追加到文件时,请考虑使用不同的序列化格式,例如 JSON Lines :
scrapy crawl giteeSpider -o project.jl-s FEED_EXPORT_ENCODING=UTF-8
使用 jl 类似于文件流方式追加数据,不会破坏 json 的数据结构,因为最外面没有使用 json 数组包裹。
在小项目中使用此方式可以方便的导出数据,但如果对项目有更复杂的需求,则可以编写一个 Item Pipeline。
2.6 快捷方式生成请求
next_page 是相对地址,通过 response.urljoin 拼接 next_page 获取完成的请求地址。
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback=self.parse)
response.follow 自动化处理地址,支持字符串、选择器对象、批量选择器对象,以下四种写法。
for href in response.css('ul.pager a::attr(href)'):
yield response.follow(href, callback=self.parse)
for a in response.css('ul.pager a'):
yield response.follow(a, callback=self.parse)
anchors = response.css('ul.pager a')
yield from response.follow_all(anchors, callback=self.parse)
yield from response.follow_all(css='ul.pager a', callback=self.parse)
2.6 爬虫接受命令行参数
运行命令
execute(['scrapy', 'runspider', 'giteeParameterSpider.py', '-o', 'projectParmete.json'
, '-s', 'FEED_EXPORT_ENCODING=UTF-8','-a','type=web-app-develop'
])
import scrapy
"""
从命令行接受 参数
"""
class giteeParameterSpider(scrapy.Spider):
name = 'giteeParameter'
start_urls = [
'https://gitee.com/explore/',
]
def start_requests(self):
url = self.start_urls[0]
# 从self 从获取属性 type,默认值 None
type = getattr(self,'type',None)
if type is not None:
url = url + type
yield scrapy.Request(url,self.parse)
def parse(self, response):
# 使用 css 选择器 div 元素 class = item
for item in response.css('div.divided.items.explore-repo__list div.item'):
try:
yield {
'title': item.css('a.title.project-namespace-path::text').get(),
'desc': item.css('div.project-desc::text').get(),
}
except TypeError:
print(TypeError)
a_list = response.css('#git-discover-page a')
next_page = a_list[len(a_list)-1].css('::attr(href)').get()
if next_page is not None:
# response.follow,url 传参可以使用如下方法,它会自动解析出地址
# 1.可以使用相对地址
# 2.可以使用选择器
# 3.可以使用标签
yield response.follow(next_page, self.parse)
效果
2.7 scrapy 中使用日志
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.logger.info('A response from %s just arrived!', response.url)
3. 四种常用的Spider
- CrawlSpider
- XMLFeedSpider
- CSVFeedSpider
- SitemapSpider
3.1 CrawlSpider
CrawlSpider
继承自Spider
, 爬取网站常用的爬虫,其定义了一些规则(rule
)方便追踪或者是过滤link。 也许该spider并不完全适合您的特定网站或项目,但其对很多情况都是适用的。 因此您可以以此为基础,修改其中的方法,当然您也可以实现自己的spider
。除了从
Spider
继承过来的(您必须提供的)属性外,其提供了一个新的属性:
- rules
一个包含一个(或多个)
Rule
对象的集合(list)。 每个Rule
对爬取网站的动作定义了特定表现。Rule
对象在下边会介绍。 如果多个rule匹配了相同的链接,则根据他们在本属性中被定义的顺序,第一个会被使用。该spider也提供了一个可复写(
overrideable
)的方法:
- parse_start_url(response)
当
start_url
的请求返回时,该方法被调用。 该方法分析最初的返回值并必须返回一个Item
对象或者 一个Request
对象或者 一个可迭代的包含二者对象。爬取规则
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(该spider中同名的函数将会被调用)。 从link_extractor
中每获取到链接时将会调用该函数。该回调函数接受一个response作为其第一个参数, 并返回一个包含 Item 以及(或) Request 对象(或者这两者的子类)的列表(list)。当编写爬虫规则时,请避免使用
parse
作为回调函数。 由于CrawlSpider
使用 parse 方法来实现其逻辑,如果 您覆盖了 parse 方法,crawl spider 将会运行失败。
cb_kwargs
: 包含传递给回调函数的参数(keyword argument)的字典。follow
: 是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果 callback 为None, follow 默认设置为 True ,否则默认为 False 。process_links
: 是一个callable或string(该spider中同名的函数将会被调用)。 从link_extractor中获取到链接列表时将会调用该函数。该方法主要用来过滤。process_request
: 是一个callable或string(该spider中同名的函数将会被调用)。 该规则提取到每个request时都会调用该函数。该函数必须返回一个request或者None。 (用来过滤request)
爬取 CSDN 的文章,进入详情页面 获取文件url 和 文章的 title,并在 详情页面继续 匹配 r'./article/.' ,获取文章。
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class DoubanSpider(CrawlSpider):
name = 'csdn'
allowed_domains = ['blog.csdn.net']
start_urls = ['https://blog.csdn.net']
# 指定链接提取的规律
rules = (
# allow 设置匹配链接的正则表达式
# callback 设置回调的方法
# follow:是指爬取了之后,是否还继续从该页面提取链接,然后继续爬下去
Rule(LinkExtractor(allow=r'.*/article/.*'), callback='parse_item', follow=True),
)
def parse_item(self, response):
print('-'*100)
yield {
'url' : response.url,
'title' : response.css('h1::text').extract_first()
}
爬取结果如下
经典案例,爬取分页数据
Spider 写法
import scrapy
class GiteespiderSpider(scrapy.Spider):
# 爬虫名称,在项目中是唯一的
name = 'giteeSpider'
# 过滤爬取的域名,不在此允许范围内的域名就会被过滤,而不会进行爬取
# start_urls 首次请求不会被过滤
allowed_domains = ['gitee.com']
# 待爬取的地址
start_urls = ['http://gitee.com/explore/develop-tools/']
# 默认回调方法
def parse(self, response):
# 使用 css 选择器 div 元素 class = item
for item in response.css('div.divided.items.explore-repo__list div.item'):
try:
yield {
'title' : item.css('a.title.project-namespace-path::text').get(),
'desc' : item.css('div.project-desc::text').get()
}
except TypeError:
print(TypeError)
a_list = response.css('#git-discover-page a')
next_page = a_list[len(a_list) - 1].css('::attr(href)').get()
if next_page is not None:
# response.follow,url 传参可以使用如下方法,它会自动解析出地址
# 1.可以使用相对地址
# 2.可以使用选择器
# 3.可以使用标签
yield response.follow(next_page, self.parse)
CrawlSpider 写法
减少了 去查询 每个分页的步骤,代码更简洁。
适用于 批量获取 有规律的 链接。
import scrapy
from scrapy.spiders import Rule, CrawlSpider
from scrapy.linkextractors import LinkExtractor
class giteeCrawlSpider(CrawlSpider):
# 爬虫名称,在项目中是唯一的
name = 'giteeCrawlSpider'
# 域名
allowed_domains = ['gitee.com']
# 开始链接
start_urls = ['https://gitee.com/explore/develop-tools']
# 定义提取url地址规则,---这理 可以提取出 所有分页
rules = (
# LinkExtractor 链接提取器,提取url地址,提取到的url地址交给parse函数发送请求。
# LinkExtractor 有一个集合(集合是唯一的),在当前响应中提取到的链接会放进去,并进行判重操作,如果有,就不会再提取。
# callback 提取出来的url地址的response会交给callback处理 callback后面必须是字符串
# follow 当前url地址的响应能够重新进入rules(经过rules包含的每一个Rule)来提取url地址。follow和callback看情况而写
# 如果callback为None,follow默认设置为True,否则默认为False。 这种逻辑的原因:不需要callback解析的一般是翻页或者跳板链接,自然需要follow跟进。
# Rule之间不能传递参数 只有手动yield才能通过meta传参
# url地址不完整 crawlspider会自动补充完整之后再请求
Rule(LinkExtractor(allow=r'.+page=\d'), follow=True,callback="parse_item"),
)
# parse函数有特殊功能,parse函数在父类里面有定义,crawlspider中不能定义parse函数,否则会重写父类方法
def parse_item(self, response):
# 使用 css 选择器 div 元素 class = item
for item in response.css('div.divided.items.explore-repo__list div.item'):
try:
yield {
'title': item.css('a.title.project-namespace-path::text').get(),
'desc': item.css('div.project-desc::text').get()
}
except TypeError:
print(TypeError)
3.2 XmlFeedSpider
XmlFeedSpider 是 Scrapy 的通用爬虫之一,主要用于爬取xml 格式的数据。
常用设置:
iterator 定义要使用的迭代器的字符串, 默认值为 iternodes,可选项如下:
iternodes
– 基于正则表达式的快速迭代器html
– 使用 Selector 的迭代器。特别提醒,它使用DOM解析,并且必须在内存中加载所有DOM,当数据量大的时候可能会产生问题。xml
– 使用 Selector 的迭代器。特别提醒,它使用DOM解析,并且必须在内存中加载所有DOM,当数据量大的时候可能会产生问题。
itertag
具有要迭代的节点(或元素)名称的字符串,例如:
itertag = 'product'
namespaces
一个由 (prefix, url) 元组(tuple)所组成的list。 其定义了在该文档中会被spider处理的可用的命名空间。 prefix 及 uri 会被自动调用 register_namespace()函数注册命名空间。
您可以通过在 itertag
属性中指定节点的 namespace。
例如:
class YourSpider(XMLFeedSpider):
namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
itertag = 'n:url'
# ...
除了这些新的属性之外,该spider
也有以下可以覆盖(overrideable)的方法:
adapt_response(response)
该方法在spider分析response前被调用。您可以在response被分析之前使用该函数来修改内容(body)。 该方法接受一个response并返回一个response(可以相同也可以不同)。
parse_node(response, selector)
当节点符合提供的标签名时(itertag)该方法被调用。 接收到的response以及相应的 Selector 作为参数传递给该方法。 该方法返回一个 Item 对象或者 Request 对象 或者一个包含二者的可迭代对象(iterable)。
process_results(response, results)
当spider返回结果(item或request)时该方法被调用。 设定该方法的目的是在结果返回给框架核心(framework core)之前做最后的处理, 例如设定item的ID。其接受一个结果的列表(list of results)及对应的response。 其结果必须返回一个结果的列表(list of results)(包含Item或者Request对象)。
爬取xml 格式数据,获取到 food 节点下,name、price、description、calories 的值。
代码实现
from scrapy.spiders import XMLFeedSpider
class XmlFeedSpider(XMLFeedSpider):
name = 'xmlFeedSpider'
allowed_domains = ['example.com']
start_urls = ['https://www.w3school.com.cn/example/xmle/simple.xml']
iterator = 'iternodes'
itertag = 'food'
def parse_node(self, response, node):
yield {
'name' : node.xpath('.//name/text()').get(),
'price': node.xpath('.//price/text()').get(),
'description': node.xpath('.//description/text()').get(),
'calories': node.xpath('.//calories/text()').get()
}
main.py 文件
from scrapy.cmdline import execute
import os
import sys
"""
启动文件用来代替命令行启动,文件放在项目根目录下
"""
if __name__ == '__main__':
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
execute(['scrapy', 'runspider', 'XmlFeedSpider.py', '-o', 'xml.json'
, '-s', 'FEED_EXPORT_ENCODING=UTF-8'
])
抓取效果
3.3 CSVFeedSpider
CSVFeedSpider 是Scrapy 的通用爬虫之一,主要用于爬取csv文件,方便解析,它的解析方式 是按行(row) 解析,与XMLFeedSpider 十分类似,默认回调函数是 parse_row()
常用配置:
delimiter
在CSV文件中用于区分字段的分隔符,类型为string,默认值为逗号(‘,’)。
headers
一个列表,包含CSV文件中需要提取的字段名称。
parse_row(response, row)
该方法用于接收一个
response
对象以及一个header
字段为键的字典。您也可以覆盖adapt_response
及process_results
方法来进行预处理和后处理。
爬取案列 csv 文件 sts_tenant.csv
代码实现:
from scrapy.spiders import CSVFeedSpider
class CsvSpider(CSVFeedSpider):
name = 'csvSpider'
allowed_domains = ['novaq-resource.oss-cn-hangzhou.aliyuncs.com']
start_urls = ['https://novaq-resource.oss-cn-hangzhou.aliyuncs.com/sys_tenant.csv']
# delimiter = ';'
# quotechar = ""
headers = ['tenant_id','tenant_name','short_name','tenant_remark','delete_flag',
'start_time','end_time','contract','contact','contact_phone','contact_email',
'num_people','num_group','invespec_admin_user_id','admin_user_id','type','vip_id',
'audit_status','tenant_logo','country','rsv1','rsv2','rsv3','create_time',
'create_user','modify_time','modify_user']
def parse_row(self, response, row):
self.logger.info('Hi, this is a row!: %r', row)
print(row['tenant_name'])
yield {
'tenant_name' : row['tenant_name'],
'contact': row['contact'],
'invespec_admin_user_id': row['invespec_admin_user_id'],
'delete_flag': row['delete_flag'],
}
实现效果
3.4 SitemapSpider
SitemapSpider 可以轻松爬取 Sitemap(站点地图) 的数据,常用的属性如下:
sitemap_urls
指向要爬网其URL的网站地图的URL列表。您也可以指向 robots.txt 它将被解析为从中提取站点地图URL。
sitemap_rules
元组列表,包含正则表达式和回调函数,格式是这样的 (regex,callback)。regex 可以是正则表达式,也可以是一个字符串。 callback 用于处理 url 的回调函数;
sitemap_follow
指定需要跟进 Sitemap 的正则表达式列表;
sitemap_alternate_links
当指定的 url 有可选的链接时是否跟进,默认不跟进。这里所谓的可选链接指的是备用网址,一般的格式如下:
sitemap_filter
(entries)[源代码]这是一个过滤器函数,可以重写该函数以根据其属性选择站点地图条目。
网站有一个 Sitemap(站点地图) 文件包含整个网站的每个网址链接,其中包含了上次更新时间、更新频率以及网址的权重(重要程度)常见的 Sitemap 文件格式有 TXT 、 XML 和 HTML 格式,大部分网站是以 XML 格式来显示的。下面我们来看一下 CSDN 网站的 Sitemap 文件格式。
爬取 第一个链接 beautifulsoup4 的数据内容,并爬取下图,左侧目录名称。
代码实现:
from scrapy.spiders import SitemapSpider
class SitemapSpider(SitemapSpider):
name = 'sitemapSpider'
sitemap_urls = ['https://www.csdn.net/sitemap.xml']
sitemap_rules = [
# 指定匹配值 和 回调函数
('beautifulsoup4', 'parse')
]
def parse(self, response):
docs = response.css('.local-toc li')
for doc in docs:
yield {
"title": doc.css(".reference::text").extract_first(),
"url" : doc.css(".reference::attr(href)").extract_first()
}
爬取数据结果。
评论区