目 录CONTENT

文章目录

Scrapy 爬虫入门_基础功能

小张的探险日记
2021-09-07 / 0 评论 / 1 点赞 / 495 阅读 / 12,125 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2021-12-28,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1.什么是Scrapy?

Scrapy是一个应用程序框架,用于对网站进行爬行和提取结构化数据,这些结构化数据可用于各种有用的应用程序,如数据挖掘、信息处理或历史存档。

尽管Scrapy最初是为 web 抓取 它还可以用于使用API提取数据(例如 Amazon Associates Web Services )或者作为一个通用的网络爬虫。

1.1 简单案例

​ 安装 scrapy

pip install scrapy

image-20210517004104302

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 实现效果

image-20210517231648796

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

得到一个项目,结构如下

image-20210518001537494

2.2 新建一个爬虫

根据指引,新建一个爬虫程序,并指定目标网站。

image-20210518001649521

爬虫程序搭建完成。

image-20210518001905677

设置打印 返回的响应结果

image-20210518003734398

2.3 运行爬虫

​ 运行爬虫 scrapy crawl giteeSpider ,此处giteeSpider 为 爬虫名称

​ 打印日志 太长截取一部分。

image-20210518003844181

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)

爬取数据效果

image-20210518011733780

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 数组包裹。

image-20210518234859630

在小项目中使用此方式可以方便的导出数据,但如果对项目有更复杂的需求,则可以编写一个 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)

效果

image-20210519001913919

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

  1. CrawlSpider
  2. XMLFeedSpider
  3. CSVFeedSpider
  4. 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()
        }

爬取结果如下

image-20210519234223879

经典案例,爬取分页数据

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 的值。

image-20210520010827516

代码实现

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'
             ])

抓取效果

image-20210520011022194

3.3 CSVFeedSpider

​ CSVFeedSpider 是Scrapy 的通用爬虫之一,主要用于爬取csv文件,方便解析,它的解析方式 是按行(row) 解析,与XMLFeedSpider 十分类似,默认回调函数是 parse_row()

​ 常用配置:

​ delimiter

​ 在CSV文件中用于区分字段的分隔符,类型为string,默认值为逗号(‘,’)。

​ headers

​ 一个列表,包含CSV文件中需要提取的字段名称。

​ parse_row(response, row)

​ 该方法用于接收一个response对象以及一个header字段为键的字典。您也可以覆盖 adapt_responseprocess_results 方法来进行预处理后处理

​ 爬取案列 csv 文件 sts_tenant.csv

image-20210523231356946

代码实现:

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'],
        }

​ 实现效果

image-20210523235410385

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 文件格式。

image-20210523235553971

​ 爬取 第一个链接 beautifulsoup4 的数据内容,并爬取下图,左侧目录名称。

image-20210523235653587

代码实现:

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()
            }

爬取数据结果。

image-20210523235855640

1

评论区