scrapy基础(持续更新)

环境:
Python3.7
scrapy2.4
官方文档(英文)scrapy2.4:https://docs.scrapy.org/en/latest/intro/overview.html
官方文档(中文)scrapy2.3:https://www.osgeo.cn/scrapy/intro/overview.html
笔记参照:"从零开始学scrapy网络爬虫" 书籍;  作者:张涛;

在我理解来看,爬虫无非就三个步骤:1、发出请求。2、解析数据。3、存储数据。

1、Scrapy 框架结构及执行流程

1.1、组件

  • 引擎(Engine)

    引擎犹如总指挥,是整个系统的大脑,只会其他组件协同工作。

  • 调度器(Scheduler)

    调度器接收引擎发过来的请求,按照先后顺序,压入队列中,同时去除重复的请求。

  • 下载器(Downloader)

    下载器用于下载页面内容,并将网页内容返回给爬虫(Scrapy下载器是建立在 twisted 这个高效的异步模型上的)。

  • 爬虫(Spiders)

    爬虫作为最核心的组件,用于从特定的网页中提取需要的信息,即所谓的实体(Item)。用户也可以从中提取出链接,让 Scrapy 继续抓取下一个页面。

  • 项目管道(Item Pipelines)

    项目管道负责处理爬虫从网页中抽取的实体。主要的功能是持久化实体、验证实体的有效性、清除不需要的数据等。

  • 下载器中间件(Downloader Middlewares)

    下载器中间件介于引擎和爬虫和之间,下载器中间件介于引擎和下载器之间的请求及响应。

  • 爬虫中间件(Spider Middlewares)

    爬虫中间件介于引擎和爬虫之间,主要工作是处理爬虫的响应输入和输出。

1.2、数据流

Scrapy 中定义的 Request 和 Response 类,用于保存请求和响应数据;Item 类保存解析后的结构数据。

1.3、Scrapy 执行流程

下面从数据流的角度介绍 Scrapy 的结构数据:

  1. 爬虫(Spider)使用 URL(要抓取页面的网址)构造一个请求(request)对象,提交给引擎(Engine)。如果请求要伪装成浏览器,或者设置代理 IP,可以先在爬虫中间件中设置,再发送给引擎。
  2. 引擎将请求安排给调度器,调度器根据请求的优先级确定执行顺序。
  3. 引擎从调度器获取即将要执行的请求。
  4. 引擎通过下载器中间件,将请求发送给下载器下载页面。
  5. 页面完成后,下载器会生成一个响应(Response)对象并将其发送给引擎。下载后的数据会保存于响应对象中。
  6. 引擎接收来自下载器的响应对象后,通过爬虫中间件,将其发送给爬虫(Spider)进行处理。
  7. 爬虫将抽取到的一条数据实体(Item)和新的请求(如下一页的链接)发送给引擎。
  8. 引擎将爬虫获取到 Item 发送给项目管道(Item Pipelines),项目管道实现数据持久化等功能。同时将新的请求发送给调度器,再从第 2 步开始重复执行,直到调度器中没有更多的请求,引擎关闭该网站。

2、基础

第一个网络爬虫

2.1、创建项目

scrapy startproject qidian_hot     # qidian_hot 是项目名称

在这里插入图片描述

目录下就会生成一个文件夹,这个文件夹就是项目了。

2.2、编写代码

解析数据主要写在 spiders 文件夹里面,现在我们在文件夹里面创建一个新的 .py 文件。

# spiders/qidian_hot_spider.py
# -*- coding:utf-8 -*-
from scrapy import Request
from scrapy.spiders import Spider


class HotSalesSpider(Spider):
    # 定义爬虫名称
    name = 'hot'  # 这个名字用于启动 Scrapy 项目的。
    # 起始的 URL 列表
    start_urls = ['https://www.qidian.com/rank/hotsales?style=1']
    # 如果想要抓取多页数据,可这样写
    # start_urls = [
    #     'https://www.qidian.com/rank/hotsales?style=1',
    #     'https://www.qidian.com/rank/hotsales?style=1&page=2',  # page是翻页的参数
    #     'https://www.qidian.com/rank/hotsales?style=1&page=3',
    #     'https://www.qidian.com/rank/hotsales?style=1&page=4',
    #     'https://www.qidian.com/rank/hotsales?style=1&page=5'
    # ]

    # 解析函数
    def parse(self, response, **kwargs):
        # 使用 xpath 定位到小说内容的 div 元素,保存到列表中
        list_selector = response.xpath('//div[@class="book-mid-info"]')
        # 依次读取每部小说的元素,从中获取名称、作者、类型和形式
        for one_selector in list_selector:
            # 获取小说名
            name = one_selector.xpath('h4/a/text()').extract()[0]
            # 获取作者
            author = one_selector.xpath('p[1]/a[1]/text()').extract()[0]
            # 获取类型
            type = one_selector.xpath('p[1]/a[2]/text()').extract()[0]
            # 获取形式(连载/完本)
            form = one_selector.xpath('p[1]/span/text()').extract()[0]
            # 将爬取到的一部小说保存到字典中
            hot_dict = {
                'name': name,       # 小说名称
                'author': author,   # 作者
                'type': type,       # 类型
                'form': form        # 形式
            }
            # 使用 yield 返回字典
            yield hot_dict

上面代码注意事项:

  • **name:**必填项,name 是区分不同爬虫的唯一标识,因为一个 Scrapy可以有多个爬虫。不同的爬虫,name 值不能相同。
  • **start_urls:**存放要爬取的的目标网页地址的列表。
  • **start_requests():**爬虫启动时,引擎自动调用该方法,并且只会被调用一次,用于生成初始化的请求对象(Request)。start_requests() 方法读取 start_urls 列表中的 URL 并生成 Request 对象,发送给引擎。引擎再指挥其他组件向网站服务器发送请求,下载网页。代码中之所以没看到 start_requests() 方法,是因为我们没有重写它,直接使用了基类的功能。
  • **parse():**Spider 类的核心方法。引擎将下载好的页面作为参数传递给 parse() 方法,parse() 方法执行从页面中解析数据的功能。

2.3、运行项目

这里运行项目的名称就是上面代码中 name = ‘hot’ 的 name 值。

为了使我们的蜘蛛工作,请转到项目的顶级目录并运行:

scrapy crawl hot
scrapy crawl hot -o hot.csv   # 可以将内容输出成 csv文件

2.4、重写 start_requests() 方法

  • 重写 start_requests() 方法可以给浏览器添加一些参数,比如这里的浏览器UA。
# spiders/qidian_hot_spider.py
# -*- coding:utf-8 -*-
from scrapy import Request
from scrapy.spiders import Spider


class HotSalesSpider(Spider):
    # 定义爬虫名称
    name = 'hot'  # 这个名字用于启动 Scrapy 项目的。
    # 设置浏览器头/设置User-Agent/请求头
    qidian_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/79.0.3945.88 Safari/537.36'}

    # 获取初始化 Request
    def start_requests(self):  # 有了这个函数就不需要有 start_urls 属性去设置 URL 了
        url = 'https://www.qidian.com/rank/hotsales?style=1'
        # 生成请求对象,设置 url、headers、callback
        yield Request(url, headers=self.qidian_headers, callback=self.qidian_parse)

    # 解析函数
    def qidian_parse(self, response):  # 只是换了名字,里面的内容没变。(这个名字不是非要换)
        # 使用 xpath 定位到小说内容的 div 元素,保存到列表中
        list_selector = response.xpath('//div[@class="book-mid-info"]')
        # 依次读取每部小说的元素,从中获取名称、作者、类型和形式
        for one_selector in list_selector:
            # 获取小说名
            name = one_selector.xpath('h4/a/text()').extract()[0]
            # 获取作者
            author = one_selector.xpath('p[1]/a[1]/text()').extract()[0]
            # 获取类型
            type = one_selector.xpath('p[1]/a[2]/text()').extract()[0]
            # 获取形式(连载/完本)
            form = one_selector.xpath('p[1]/span/text()').extract()[0]
            # 将爬取到的一部小说保存到字典中
            hot_dict = {
                'name': name,       # 小说名称
                'author': author,   # 作者
                'type': type,       # 类型
                'form': form        # 形式
            }
            # 使用 yield 返回字典
            yield hot_dict

start_requests() 参数说明:

  • **url:**请求访问的网址。
  • **headers:**请求头信息。
  • **callback:**回调函数。这里确定解析数据的函数为 qidian_parse()。引擎会将下载好的页面(Response 对象)发送给该方法,执行数据解析功能。

2.5、Request 对象

Request 对象用来描述一个 HTTP 请求,它通常在 Spider 中生成并由下载器执行。

# Request 对象的源码
class Request(object_ref):

    def __init__(self, url, callback=None, method='GET', headers=None, body=None,
                 cookies=None, meta=None, encoding='utf-8', priority=0,
                 dont_filter=False, errback=None, flags=None, cb_kwargs=None):

**1、**以下参数用于设置网站发送的 HTTP 请求的内容:

  • **url:必填。**HTTP 请求的网址。
  • **method:**HTTP 请求的方法,如 GET、POST、PUT等,默认是 GET。必须是大写英文。
  • **headers:**HTTP 的 请求头,类型为字典型。请求头包含的内容:
  1. **Accept:**浏览器端可以接收的媒体类型。
    2. **Accept-Encoding:**浏览器接受的编码方式。
    3. **Accept-Language:**浏览器所接受的语言种类。
    4. **Connection:**表示是否需要持久连接。
    5. **Cookie:**有时也可以用复数 Cookies。这个是为了辨别用户身份、进行会话跟踪而储存在用户本地的数据。
    6. **Host:**指定被请求资源的 Internet 主机和端口号,通常从 URL 中提取。
    7. **User-Agent:**模拟浏览器头。
  • **body:**HTTP 的请求体,类型为 str 或 unicode。
  • **cookies:**请求的 Cookie 值,类型为字典型或列表型,可以实现自动登录的效果。
  • **encoding:**请求的编码方式,默认为 utf-8。

**2、**以下参数设置 Scrapy 框架内部的事务:

  • **meta:**字典类型,用于数据的传递。它可以将数据传递给其他组件,也可以传递给 Response 对象。
  • **priority:**请求的优先级,默认为 0,优先级越高的请求会优先下载。
  • **dont_filter:**如果对同一个 url 多次提交相同的请求,可以使用此项来忽略重复的请求,避免重复下载,其默认值为 False。如果设置为 True,即使是重复的请求,也会强制下载,例如爬取实时变化的股票信息数据。
  • **errback:**在处理请求时引发任何异常时调用的函数,包括 HTTP 返回的 404。
  • **flags:**发送到请求的标志可用于日志记录或类似用途。
  • **cb_kwargs:**具有任意数据的dict,将作为关键字参数传递到请求的回调。

2.6、递归下一页链接

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response, **kwargs):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').getall(),
                'author': quote.css('span small::text').getall(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }

        next_page = response.css('li.next a::attr(href)').get()  # 获取下一页链接
        if next_page is not None:
            next_page = response.urljoin(next_page)  # 因为上面获取到的链接是相对路径的,urljoin方法可以补齐头部路径。
            yield scrapy.Request(next_page, callback=self.parse)  # 跳转到下一页链接,发出请求,callback=回调到这个函数。

Request() 方法只支持绝对路径的链接。

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response, **kwargs):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').getall(),
                'author': quote.css('span small::text').getall(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }

        next_page = response.css('li.next a::attr(href)').get()  # 获取下一页链接
        if next_page is not None:
            yield response.follow(next_page, callback=self.parse)  # 跳转到下一页链接,发出请求,callback=回调到这个函数。
  • for 循环迭代下一页
import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response, **kwargs):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').getall(),
                'author': quote.css('span small::text').getall(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }
        url = 'http://quotes.toscrape.com'
        for i in range(2, 11):
            yield scrapy.Request(url + '/page/{}/'.format(i), callback=self.parse)  # 跳转到下一页链接,发出请求,callback=回调到这个函数。

follow() 方法支持相对路径的链接。

# 也可以将选择器直接传递给 follow()
for href in response.css('ul.pager a::attr(href)'):
    yield response.follow(href, callback=self.parse)
# 也可以直接把 a 标签给到 follow() 方法,它会自动去识别 href 属性的值。
for a in response.css('ul.pager a'):
    yield response.follow(a, callback=self.parse)
# 或者更简单的写法
yield from response.follow_all(css='ul.pager a', callback=self.parse)

更多例子

import scrapy

class AuthorSpider(scrapy.Spider):
    name = 'author'

    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response, **kwargs):
        author_page_links = response.css('.author + a')
        yield from response.follow_all(author_page_links, self.parse_author)

        pagination_links = response.css('li.next a')
        yield from response.follow_all(pagination_links, self.parse)

    def parse_author(self, response):
        def extract_with_css(query):
            return response.css(query).get(default='').strip()

        yield {
            'name': extract_with_css('h3.author-title::text'),
            'birthdate': extract_with_css('.author-born-date::text'),
            'bio': extract_with_css('.author-description::text'),
        }

这个蜘蛛将从主页开始,它将跟踪所有指向作者页面的链接,调用 parse_author 它们的回调,以及与 parse 像我们以前看到的那样回拨。
这里,我们把回电传递给 response.follow_all 作为使代码更短的位置参数;它也适用于 Request .
这个 parse_author 回调定义了一个助手函数,用于从CSS查询中提取和清理数据,并用作者数据生成python dict。
这个蜘蛛展示的另一个有趣的事情是,即使同一作者引用了很多话,我们也不需要担心多次访问同一作者页面。默认情况下,scrapy过滤掉对已经访问过的URL的重复请求,避免了由于编程错误而太多地访问服务器的问题。这可以通过设置进行配置 DUPEFILTER_CLASS .


3、解析器

  • getall():返回的是列表类型,返回值可能存在很多个结果。
  • get():返回的是字符串类型,返回结果只有第一个。注意:如果没有匹配到,将返回 None。

Xpath

常用的一些示例:

response.xpath('//title')
>> [<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]
response.xpath('//title/text()').get()
>> 'Quotes to Scrape'

获取 id 为"firstHeading"的 h1 标签下 span 中的 text。

//h1[@id="firstHeading"]/span/text()

获取 id 为"toc"的 div 标签内的无序列表(ul)中所有链接的 URL。

//div[@id="toc"]/ul//a/@href

获取 class 属性包含"ltr"以及 class 属性包含"skin-vector"的任意元素
内所有标题元素(h1)中的文本。这两个字符串可能在同一个 class 中,也可
能在不同的 class 中。

//*[contains(@class,"ltr") and contains(@class,"skin-vector")]//h1//text()		# contains 匹配一个属性值中包含的字符串

选择 class 属性值为"infobox"的表格中第一张图片的 URL。

//table[@class="infobox"]//img[1]/@src

选择 class 属性以"reflist"开头的 div 标签中所有链接的 URL。

//div[starts-with(@class,"reflist")]//a/@href      # starts-with 匹配一个属性开始位置的关键字

选择子元素包含文本"References"的元素之后的div元素中所有链接的URL。

//*[text()="References"]/../following-sibling::div//a

选择文本为 “百度搜索” 的 a 标签。

//a[text()='百度搜索']				# text() 匹配的是显示文本信息,此处也可以用来做定位用

常用路径表达式:

表达式描述实例
nodename选取此节点的所有子节点div,p,h1
/从根节点选取(描述绝对路径)/html
//不考虑位置,选择页面中所有子孙节点//div
.选取当前节点(描述相对路径)./div
选取当前节点的父节点(描述相对路径)h1/…/
@属性名获取属性的值@href,@id
text()获取元素中的文本节点//h1/text()

小技巧

response.xpath('div[@class="price"]/p[2]/text()').getall()
这行代码输出的应该是列表,但是有意思的是,输出的列表里的多个值也可以使用 text() 获取文本功能。而已列表里的值可以直接输出,scrapy会自动把括号去掉,多个值用逗号隔开
估计 css 也可以这样用

CSS

response.css('title')		返回 title 标签的对象
>> [<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]
response.css('title::text').getall()		返回 title 文本内容
>> ['Quotes to Scrape']
response.css('title').getall()				返回 title 标签
>> ['<title>Quotes to Scrape</title>']
response.css('title::text').get()			返回 title 文本内容     ::text
>> 'Quotes to Scrape'
response.css('title::text')[0].get()		同上
>> 'Quotes to Scrape'
response.css('title.asd').getall()			返回 class 属性值为 asd 的 title 标签
例子:<div class="dnskj asd"></div>
response.css('.dnskj.asd')				如果 class 的值存在空格,那么就用 . 符号来代替空格
response.css('a span')					返回 a 标签下的 span 标签
response.css('li.next a::attr(href)')		抓取class属性值为next的li标签下的a标签的href属性值  ::attr(href)
response.css('li.next a').attrib['href']     抓取class属性值为next的li标签下的a标签的href属性值  .attrib['href'],这个在xpath也可以使用
>> '/page/2/'

常用的CSS表达式

表达式描述实例
*选取所有元素*
E选取 E 元素div
E1,E2选取 E1和E2元素div,p
E1>E2选择E1的子元素E2div>h1
E1 E2选取 E1 子孙中的 E2元素div h1
.class选取 class 属性的值为 class的元素.author
#id选取ID属性的值为 div 的元素.name
[ATTR]选取包含ATTR属性的元素[href]
[ATTR=value]选取属性 ATTR 的值为 value 元素[class=author]
E:nth-child(n)选取E元素且该元素是其父元素的第n个子元素p:nth-child(1)
E:nth-last-child(n)选取E元素且该元素是其父元素的倒数第n和子元素p:nth-last-child(1)
E::text获取E元素的文本h1::text

解析器 + re

response.css('title::text').re(r'Quotes.*')
>> ['Quotes to Scrape']
response.css('title::text').re(r'Q\w+')
>> ['Quotes']
response.css('title::text').re(r'(\w+) to (\w+)')
>> ['Quotes', 'Scrape']

解析器注意事项

  • 使用的时候尽量使用相对路径,而不是绝对路径。
  • 使用的时候定位 ID 值往往是最可靠的。

4、命令行工具

创建项目

scrapy startproject myproject

创建爬虫

首先进入到项目的根目录,再执行下面命令

scrapy genspider baidu baidu.com		# baidu 是文件名也是爬虫name的名,baidu.com 就是要抓取的网站了

就会在项目路径下的 spiders 文件夹下创建新的 .py 文件。

更多命令

scrapy -h     可以查看有什么命令
project命令:(只能在项目内部使用)
bench         Run quick benchmark test(翻译:运行快速基准测试)
check         Check spider contracts(翻译:检查卡盘合同)
list          List available spiders(翻译:列出可用的蜘蛛)
edit          Edit spider(翻译:编辑蜘蛛)
parse         Parse URL (using its spider) and print the results(翻译:解析URL(使用其spider)并打印结果)
crawl         Run a spider(翻译:跑蜘蛛)

全局命令:
fetch         Fetch a URL using the Scrapy downloader(翻译:使用Scrapy下载器获取一个URL)
genspider     Generate new spider using pre-defined templates(翻译:使用预定义的模板生成新的spider)
runspider     Run a self-contained spider (without creating a project)(翻译:运行自包含的spider(不创建项目))
settings      Get settings values(翻译:获取设置值)
shell         Interactive scraping console(翻译:交互式抓取控制台)
startproject  Create new project(翻译:创建新项目)
version       Print Scrapy version(翻译:打印废版)
view          Open URL in browser, as seen by Scrapy(翻译:在浏览器中打开URL,如Scrapy所见)
详细的这里可以看:https://www.osgeo.cn/scrapy/topics/commands.html#

5、spider 提取数据

  • 具体用法在第 2、3 章已经实战过了。

spider是定义一个特定站点(或一组站点)如何被抓取的类,包括如何执行抓取(即跟踪链接)以及如何从页面中提取结构化数据(即抓取项)。换言之,spider是为特定站点(或者在某些情况下,一组站点)定义爬行和解析页面的自定义行为的地方。

对于蜘蛛来说,抓取周期是这样的:

  1. 首先生成对第一个URL进行爬网的初始请求,然后指定一个回调函数,该函数使用从这些请求下载的响应进行调用。

    要执行的第一个请求是通过调用 start_requests()(默认)生成的方法 Request 对于中指定的URL start_urls 以及 parse 方法作为请求的回调函数。

  2. 在回调函数中,解析响应(网页)并返回 item objects , Request 对象,或这些对象中的一个不可重复的对象。这些请求还将包含回调(可能相同),然后由scrappy下载,然后由指定的回调处理它们的响应。

  3. 在回调函数中,解析页面内容,通常使用 选择器 (但您也可以使用beautifulsoup、lxml或任何您喜欢的机制)并使用解析的数据生成项。

  4. 最后,从spider返回的项目通常被持久化到数据库(在某些 Item Pipeline) 或者使用 Feed 导出.

scrapy.Spider 类的一些属性和方法:

类属性(待补充)

  • name: 定义此蜘蛛名称的字符串。spider名称是scrappy定位(和实例化)spider的方式,因此它必须是唯一的。但是,没有什么可以阻止您实例化同一个蜘蛛的多个实例。这是最重要的蜘蛛属性,也是必需的。如果蜘蛛 爬取 一个域,通常的做法是在域后命名蜘蛛,无论有没有 TLD . 例如,一只爬行的蜘蛛 mywebsite.com 经常被称为 mywebsite .(就是name的值尽量是网址的域名,这样好分辨)
  • allowed_domains:包含允许此蜘蛛爬行的域的字符串的可选列表。对于不属于此列表(或其子域)中指定的域名的URL请求,如果 OffsiteMiddleware 启用。假设您的目标URL是 https://www.example.com/1.html 然后添加 'example.com' 列在名单上。
  • start_urls
官方文档有补充

6、Item 封装数据

在 spider 提取数据是字典类型的,字典使用虽然方便,但是也有它的缺陷:

  • 字段名拼写容易出错,且无法检测到这些错误。
  • 返回的数据类型无法确保一致性。
  • 不便于将数据传给其他组件(如传递给用于数据处理的 pipeline 组件)。

定义 Item 和 Field

Item 是一个简单的容器,用于收集抓取到的数据,其提供了类似于字典(dictionary-like)的API,并具有用于声明可用字段的简单语法。

# items.py
import scrapy

class QidianHotItem(scrapy.Item):
    name = scrapy.Field()  # 小说名称
    author = scrapy.Field()  # 作者
    type = scrapy.Field()  # 类型
    form = scrapy.Field()  # 形式

将已知需要抓取的数据,用字段声明。

Field 对象用于指定每个字段的元数据,并且 Field 对象对接收的数据没有任何限制。

# quotes_spider.py
# -*- coding:utf-8 -*-
from scrapy import Request
from scrapy.spiders import Spider
from ..items import QidianHotItem  # 导入模块


class HotSalesSpider(Spider):
    # 定义爬虫名称
    name = 'hot'  # 这个名字用于启动 Scrapy 项目的。
    # 设置浏览器头/设置User-Agent/请求头
    qidian_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/79.0.3945.88 Safari/537.36'}

    # 获取初始化 Request
    def start_requests(self):  # 有了这个函数就不需要有 start_urls 属性去设置 URL 了
        url = 'https://www.qidian.com/rank/hotsales?style=1'
        # 生成请求对象,设置 url、headers、callback
        yield Request(url, headers=self.qidian_headers, callback=self.qidian_parse)

    # 解析函数
    def qidian_parse(self, response):  # 只是换了名字,里面的内容没变。(这个名字不是非要换)
        # 使用 xpath 定位到小说内容的 div 元素,保存到列表中
        list_selector = response.xpath('//div[@class="book-mid-info"]')
        # 依次读取每部小说的元素,从中获取名称、作者、类型和形式
        for one_selector in list_selector:
            # 获取小说名
            name = one_selector.xpath('h4/a/text()').extract()[0]
            # 获取作者
            author = one_selector.xpath('p[1]/a[1]/text()').extract()[0]
            # 获取类型
            type = one_selector.xpath('p[1]/a[2]/text()').extract()[0]
            # 获取形式(连载/完本)
            form = one_selector.xpath('p[1]/span/text()').extract()[0]
            item = QidianHotItem()  # 实例化
            item['name'] = name  # 小说名称
            item['author']= author  # 作者
            item['type']= type  # 类型
            item['form']= form  # 形式
            # 使用 yield 返回字典
            yield item
  • 首先导入 quotes_spider.items 下的 QidianHotItem 模块。
  • 生成 QidianHotItem 的对象 item,用于保存一部小说信息。
  • 将从页面中提取到的各个字段赋予给 item。赋值方法跟 Python 字典一样,使用 key-value 的形式。key 要在于 QidianHotItem 中定义的名称一致,否则会报错,value 为各字段。Item 复制了标准的字典的 API,因此可以按照字典的形式赋值。

使用 ItemLoader 填充容器

当项目很大、提取的字段数以百计时,数据的提取规则也会越来越多,再加上还要对提取到的数据做转换处理,代码就会变得庞大,维护起来十分困难。

为了解决这个问题,scrapy 提供了项目加载器(ItemLoader)这样一个填充容器。通过填充容器,可以配置 Item 中各个字段的提取规则,并通过函数分析原始数据,最后对Item 字段赋值,使用起来非常便捷。

Item 和 ItemLoader 的区别在于:

  • Item 提供了保存抓取到的数据的容器,需要手动将数据保存于容器中。
  • ItemLoader 提供的是填充容器的机制。
# quotes_spider.py
# -*- coding:utf-8 -*-
from scrapy import Request
from scrapy.spiders import Spider
from scrapy.loader import ItemLoader  # 导入 ItemLoader 类
from ..items import QidianHotItem

class HotSalesSpider(Spider):
    # 定义爬虫名称
    name = 'hot'  # 这个名字用于启动 Scrapy 项目的。
    # 设置浏览器头/设置User-Agent/请求头
    qidian_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/79.0.3945.88 Safari/537.36'}

    # 获取初始化 Request
    def start_requests(self):  # 有了这个函数就不需要有 start_urls 属性去设置 URL 了
        url = 'https://www.qidian.com/rank/hotsales?style=1'
        # 生成请求对象,设置 url、headers、callback
        yield Request(url, headers=self.qidian_headers, callback=self.qidian_parse)

    # 解析函数
    def qidian_parse(self, response):  # 只是换了名字,里面的内容没变。(这个名字不是非要换)
        # 使用 xpath 定位到小说内容的 div 元素,保存到列表中
        list_selector = response.xpath('//div[@class="book-mid-info"]')
        # 依次读取每部小说的元素,从中获取名称、作者、类型和形式
        for one_selector in list_selector:
            # 生成 ItemLoader 实例
            # tiem 接收 QidianHotItem 实例,selector 接收一个选择器
            novel = ItemLoader(item=QidianHotItem(), selector=one_selector)
            # 使用 xpath 选择器
            novel.add_xpath("name", "h4/a/text()")
            novel.add_xpath("author", "p[1]/a[1]/text()")
            novel.add_xpath("type", "p[1]/a[2]/text()")
            # 使用 css 选择器
            novel.add_css("form", ".author span::text")
            # 将提取好的数据 load 出来,并使用 yield 返回
            yield novel.load_item()

ItemLoader.add 填充数据有三种:

  • add_xpath():用 xpath 选择器提取数据。
  • add_css():用 css 选择器提取数据。
  • add_value():直接传值。
add_value()使用方法:
novel.add_value('form', "连载")

当提取到的数据被填充到 ItemLoader 后,还需要调用 load_item() 方法给 Item 对象赋值。


Item 和 ItemLoader 的区别在于

  • Item 提供了保存抓取到的数据的容器,需要手动将数据保存于容器中。
  • ItemLoader 提供的是填充容器的机制。

进一步的清洗数据

  1. 使用 ItemLoader 提取的数据,也是保存于列表中的,以前可以通过 extract_first() 或者 extract() 获取列表中的数据,但是在 ItemLoader 中如何实现?看一下 ItemLoader 生成的数据格式:

    {'author': ['防守打'], 'form': ['连载'], 'name': ['修炼'], 'type': ['玄幻']}
    
  2. 很多时候我们还需要做额外的数据处理,比如:去除空格、提取数字或格式化数据等。这些又如何处理?

解决的办法就是,使用输入处理器(input_processor)和输出处理器(ouput_processor)对数据的输入和输出进行解析。

例子:

# items.py
import scrapy
from scrapy.loader.processors import TakeFirst

# 定义一个转换小说形式的函数
def form_convert(form):
    if form[0] == '连载':
        return 'LZ'
    else:
        return 'WJ'

class QidianHotItem(scrapy.Item):
    name = scrapy.Field(output_processor=TakeFirst())  # 小说名称
    author = scrapy.Field(output_processor=TakeFirst())  # 作者
    type = scrapy.Field(output_processor=TakeFirst())  # 类型
    form = scrapy.Field(input_processor=form_convert, output_processor=TakeFirst())  # 形式
    # input_processor、output_processor输入和输出处理器,
    # TakeFirst()函数是scrapy内置的处理器,用来获取集合中第一个非空值。

执行流程:

form 为小说形式的字段,scrapy.Field() 的参数 input_processor 绑定了函数 form_convert()。但 ItemLoader 通过选择器(xpath或css)提取某字段数据后,就会将其发送给输入处理器进行处理,然后将处理完的数据发送给输出处理器做最后一次处理。最后调用 load_item() 函数将数据填充进 ItemLoader,并得到填充后的 Item 对象。

  1. 提取数据
  2. 输入处理器(input_processor )处理数据
  3. 输出处理器(output_processor)处理数据
  4. 调用load_item() 填充 ItemLoader
  5. 得到填充后的 Item 对象

然后我们来运行一下这个案例。就可以发现 form 字段的值(就是 form 字段那一栏)已经被替换了,原本的连载替换成了 LZ,完结的替换成了 WJ。


7、使用 Pipeline 处理数据

有时候可能还需要对数据进行处理,例如过滤掉重复数据、验证数据的有效性,以及将数据存入数据库等。接下来学习如何处理,

scrapy 的 Item Pipeline(项目管道)是用于处理数据的组件。

Item Pipeline 介绍

**当 Spider 将收集到的数据封装为 Item 后,将会被传递到 Item Pipeline(项目管道)组件中等待进一步处理。scrapy 犹如一个爬虫流水线,Item Pipeline 是流水线的最后一道工序,但它是可选的,默认关闭,使用时需要将它激活。**如果需要,可以定义多个 Item Pipeline 组件,数据会依次访问每个组件,执行相应的数据处理功能。

Item Pipeline 典型的应用:

  • 清理数据。
  • 验证数据的有效性。
  • 查重并丢弃。
  • 将数据按照自定义的格式存储到文件中。
  • 将数据保存到数据库中。

Item Pipeline 方法

Item Pipeline 中,除了必须要有的 process_item() 方法外,还有3个比较常用的方法,可根据需求选择实现。

  • **open_spider(self, spider)方法:**当 Spider 开启时(爬取数据之前),该方法被调用,参数 spider 为被开启的 Spider。该方法通常用于在数据之前完成某些初始化工作,如打开文件和连接数据库等。
  • **close_spider(self, spider)方法:**当 Spider 被关闭时(所有数据被爬取完毕),该方法被调用,参数 spider 为被关闭的 Spider。该方法通常用于在数据处理完毕后,完成某些清理工作,如关闭文件和关闭数据库等。
  • **from_crawler(cls, crawler)方法:**该方法被调用时,会创建一个新的 Item Pipeline 对象,参数 crawler 为使用当前管道的项目。该方法通常用于提供对 Scrapy 核心组件的访问,如访问项目配置设置文件 settings.py。

编写 Item Pipeline

编写自己的 Item Pipeline 组件其实很简单,它只是一个实现了几个简单方法的 Python 类。当建立一个项目后,这个类就已经自动创建了。

例子:将小说的形式以首字母展现。

# pipeline.py
class QidianHotPipeline:
    def process_item(self, item, spider):
        # 判断小说形式是连载还是完结
        if item['form'] == '连载':
            item['form'] = 'LZ'
        else:
            item['form'] = 'WJ'
        return item

QidianHotPipeline 是自动生成的 Item Pipeline 类,它无须继承特定的类,只需要实现某些特定的方法,如 process_item()、open_item() 和 close_spider()。注意:方法名不可改变。

process_item() 方法是 Item Pipeline 类的核心方法,必须要实现,用于处理 Spider 爬取到得每一条数据(Item)。他有两个参数:

  • item:待处理的 Item 对象。
  • spider:爬取此数据的 Spider 对象。

方法的返回值是处理后的 Item 对象,返回的数据会传递给下一级的 Item Pipeline(如果有的情况下)继续处理。


启用 Item Pipeline

编写完了 Item Pipeline ,就需要去启用它了,因为它默认是关闭的。

启动它也很简单,只需要在 settings.py 里将代码的注释打开就好。

# settings.py

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'qidian_hot.pipelines.QidianHotPipeline': 300,
}

ITEM_PIPELINES 是一个字典,将想要启用的 Item Pipeline 添加到这个字典中。其中,键是 Item Pipeline 类的导入路径,值是一个整数值。如果启用多个 Item Pipeline,这些值就决定了它们运行的顺序,数值越小,优先级越高。


多个 Item Pipeline

场景:如果有这样一个需求,同一个作者只能上榜一部作品,而爬取到的数据中可能有多同一作者的作品。因此可以实现一个去重处理的 Item Pipeline,将重复数据过滤掉。

在 pipeline.py 中再定义一个去重的 Item Pipeline 类的 DuplicatesPipeline:

# pipelines.py
from scrapy.exceptions import DropItem

class DuplicatesPipeline:
    def __init__(self):
        # 定义一个保存作者姓名的集合
        self.author_set = set()

    def process_item(self, item, spider):
        if item['author'] in self.author_set:
            # 抛弃重复的 Item 项
            raise DropItem('查找到重复姓名的项目:%s' % item)
        else:
            self.author_set.add(item['author'])
        return item
# settings.py
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'qidian_hot.pipelines.DuplicatesPipeline': 100,
   'qidian_hot.pipelines.QidianHotPipeline': 300,
}

在构造函数 __init__() 中定义一个保存作者姓名的集合 author_set()。

在 process_item() 方法中,判断 item 中的 author 字段是否已经存在于集合 author_set 中,如果不存在,则将 item 中的 author 字段存入 author_set 集合中;如果存在,就是重复数据,使用 raise 抛出一个 DropItem 异常,该 Item 就会被抛弃,不会传递给后面的 Item Pipeline 继续处理,更不会导出到文件中。

最后 settings 配置里的 ITEM_PIPELINES:

DuplicatesPipeline 设置的值比 QidianHotPipeline 小,因此优先执行 DuplicatesPipeline ,过滤掉重复项,再将非重复项传递 QidianHotPipeline 继续处理。

  • 得出的一点心得:Item 应该就是数据了。数据是先经过 items.py 文件,然后再到 pipelines.py。

保存为其他类型文件

用命令行将数据保存到本地的格式有 CSV、JSON、XML等。但是如果有特殊要求,比如这里要求保存为 TXT 文件,并且字段之间使用其他间隔符(例如分号),这样就需要自己实现了。

下面通过 Item Pipeline 实现上面的那个特殊要求:

# pipelines.py
class SaveToTextPipeline:
    file_name = "hot.txt"  # 文件名称
    file = None  # 文件对象

    # Spider 开启时,执行打开文件操作
    def open_spider(self, spider):
        # 以追加形式打开文件
        self.file = open(self.file_name, 'a', encoding='utf-8')

    # 数据处理
    def process_item(self, item, spider):
        # 获取 item 中的各个字段,将其连接成一个字符串
        # 字段之间用分号隔开
        # 字符串末尾要有换行符 \n
        novel_str = item['name'] + ";" + item['author'] + ";" + \
                    item['type'] + ";" + item['form'] + "\n"
        # 将字符串写入文件中
        self.file.write(novel_str)
        return item

    # Spider 关闭时,执行关闭文件操作
    def close_spider(self, spider):
        # 关闭文件
        self.file.close()

分析 SaveToTextPipeline 类的几个方法:

  1. 属性 file_name 定义文件名称,属性 file 定义文件对象,便于操作文件。
  2. open_spider() 方法实现文件的打开操作,在数据处理前执行一次。
  3. close_spider() 方法实现文件的关闭操作,在数据处理后执行一次。
  4. process_item() 方法实现数据的写入操作。首先,获取 item 中的所有字段,将它们连成字符串,字段之间用分号间隔,而且字符串末尾要加上换行符(\n),实现一行显示一条数据;然后,使用 Python 的 write() 函数将数据写入文件中。

然后去 settings.py 里添加这个 Pipeline。

# settings.py
ITEM_PIPELINES = {
   'qidian_hot.pipelines.DuplicatesPipeline': 100,
   'qidian_hot.pipelines.QidianHotPipeline': 300,
   'qidian_hot.pipelines.SaveToTextPipeline': 400,  # new
}
运行命令:scrapy crawl hot

就可以输出文件了


统一将配置信息都写在 settings 里

为了便于管理,scrapy 中将各种配置信息放在配置文件 settings.py 中。上面的文件夹名也可以转移到 settings.py 中配置,下面来看看修改方法:

# settings.py
FILE_NAME = 'hot.txt'
# pipelines.py
class SaveToTextPipeline:
    # file_name = "hot.txt"  # 文件名称
    file = None  # 文件对象

    @classmethod
    def from_crawler(cls, crawler):
        # 获取配置文件中的 FILE_NAME 的值
        # 如果获取失败,就使用默认值 'hot2.txt'
        cls.file_name = crawler.settings.get('FILE_NAME', 'hot2.txt')
        return cls()

    # Spider 开启时,执行打开文件操作
    def open_spider(self, spider):
        # 以追加形式打开文件
        self.file = open(self.file_name, 'a', encoding='utf-8')

    # 数据处理
    def process_item(self, item, spider):
        # 获取 item 中的各个字段,将其连接成一个字符串
        # 字段之间用分号隔开
        # 字符串末尾要有换行符 \n
        novel_str = item['name'] + ";" + item['author'] + ";" + \
                    item['type'] + ";" + item['form'] + "\n"
        # 将字符串写入文件中
        self.file.write(novel_str)
        return item

    # Spider 关闭时,执行关闭文件操作
    def close_spider(self, spider):
        # 关闭文件
        self.file.close()

SaveToTextPipeline 类增加了 from_crawler() 方法,用于获取配置文件中的文件名。注意,该方法是一个类方法(@classmethod),scrapy 会调用该方法来创建 SaveToTextPipeline 对象,它有两个参数:

  • cls:SaveToTextPipeline 对象。
  • crawler:scrapy 中的核心对象,通过它可以访问配置文件(settings.py)。

from_crawler() 方法必须返回一个新生成的 SaveToTextPipeline 对象(return cls)。


8、数据库存储

MySQL 数据库

python 访问 MySQL 数据库

Python3:mysqlclient 模块。

连接 MySQL 数据库服务器

import MySQLdb
db_conn = MySQLdb.connect(
    db = 'qidian',  # 数据库名
    host = 'localhost',  # 主机
    user = 'root',
    password = '1234',
    charset = 'utf8'  # 编码格式
)  # 返回的 db_conn 是 Connection 对象。

获取操作游标

db_cursor = db_conn.cursor()  # 返回的是 cursor 对象,用于执行 SQL 语句。

执行 SQL 语句(增、删、改、查)

  • 新增数据
sql = 'insert into hot(name, author, type, form)values("道君", "未知", "仙侠", "连载")'
db_cursor.execute(sql)
  • 修改数据
sql = 'update hot set author = "跃千愁" where name = "道君"'
db_cursor.execute(sql)
  • 查询表 hot 中 type 为仙侠的数据
sql = 'select * from hot where type="仙侠"'
db_cursor.execute(sql)
  • 删除表中 type 为 仙侠的数据
sql = 'delete from hot where type="仙侠"'
db_cursor.execute(sql)

回滚

在数据库执行更新操作(update、insert、delete)的过程中,如果遇到错误,可以使用 collback() 方法将数据恢复到更新前的状态。这就是所谓的原子性,即要么完整地被执行,要么完全不执行。

db_conn.rollback()

需要注意:回滚操作一定要在 commit() 方法之前执行,否则就无法恢复了。

需要注意:回滚操作一定要在 commit() 方法之前执行,否则就无法恢复了。

需要注意:回滚操作一定要在 commit() 方法之前执行,否则就无法恢复了。

提交数据

调用 Connection 对象的 commit() 方法实现数据的提交。前面虽然通过 execute() 方法执行 SQL 语句完成了对数据库的更新操作,但是并未真正更新到数据库中,需要通过 commit() 方法实现对数据库的永久修改。

db_conn.commit()

关闭游标及数据库

当执行完对数据库的所有操作后,不要忘了关闭游标和数据库对象:

db_cursor.close()  # 关闭游标
db_conn.close()  # 关闭数据库

项目案例

以起点小说为例,将小说信息存储到 MySQL 中。

在 pipelines.py 中,定义一个 MySQLPipeline 类,用于实现对 MySQL 数据库的操作。

  • 需要自己先创建数据库,然后创建数据库、数据表。
# settings.py
MYSQL_DB_NAME = "db"  # 数据库名
MYSQL_HOST = "localhost"
MYSQL_USER = "root"
MYSQL_PASSWORD = "19971996"
# pipelines.py
import MySQLdb

class MySQLPipeline:
    # Spider 开启时,执行连接数据库功能
    def open_spider(self, spider):
        # 获取 settings.py 配置信息
        db_name = spider.settings.get("MYSQL_DB_NAME", "db")
        host = spider.settings.get("MYSQL_HOST", "localhost")  # 或许 settings 配置的信息
        user = spider.settings.get("MYSQL_USER", "root")
        pwd = spider.settings.get("MYSQL_PASSWORD", "19971996")
        # 连接 MySQL 数据库服务器
        self.db_conn = MySQLdb.connect(db=db_name, host=host, user=user, 
            password=pwd, charset='utf8')
        # 使用 cursor() 方法获取操作游标
        self.db_cursor = self.db_conn.cursor()

    # 数据处理
    def process_item(self, item, spider):
        # 获取 item 中的各个字段,并保存成元祖
        values = (item['name'], item['author'], item['type'], item['form'])
        # 设计插入操作的 sql 语句
        sql = 'insert into hot(name, author, type, form)values(%s, %s, %s, %s)'
        # 执行 SQL 语句,实现插入功能
        self.db_cursor.execute(sql, values)
        return item

    # Spider 关闭时,执行数据库关闭工作
    def close_spider(self, spider):
        # 关闭文件
        self.db_conn.commit()  # 提交数据
        self.db_cursor.close()  # 关闭游标
        self.db_conn.close()  # 关闭数据库
# settings.py
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'qidian_hot.pipelines.DuplicatesPipeline': 100,
   'qidian_hot.pipelines.QidianHotPipeline': 300,
   'qidian_hot.pipelines.MySQLPipeline': 400,  # 记得设置这个,设置了才会启动。
}

MongoDB 数据库(待补充)

Python 访问 MongoDB 数据库

  • pip install pymongo

连接数据库

import pymongo
# 方式一:默认的host 和port
db_client = pymongo.MonClient()

# 方式二:自定义 host 和 port 参数
db_client = pymongo.MonClient(host='localhost', port=27017)

# 方式三:使用标准的 URI 连接语法
db_client = pymongo.MonClient('mongodb://localgost:207017/')

以上三种方式都可以连接 MongoDB 数据库。


指定数据库

MongoDB 可以建立多个数据库,因此需要指定要操作的数据库:

db = db_client['qidian']

**db_client 是上一步中连接数据库服务器得到的(client)对象,db是返回的数据库对象。**当然还可以这样指定数据库:

db = db_client.qidian

上面两种方式都可以使用。注意:如果指定的数据库不存在,会自动创建一个该名称的数据库,这是 MongoDB 比较灵活的地方。


指定集合(相当于关系型数据库中的表)

MongoDB 中的一个数据库可以包含多个集合(一个数据库可以有多个集合),这跟关系型数据库中的表一个道理(一个数据库可以有多个表),所以也需要指定要操作的集合:

db_collection = db['hot']

**db 是上一步中得到的数据库对象,db_collection 是返回的集合(Collection)对象。**对集合的各种操作,都是通过这个 Collection 对象来完成的。注意:如果指定的集合不存在,在操作集合时就会自动创建一个集合。


插入文档

插入与条件匹配的单个文档。

如果想添加一个小说文档到集合 hot 中,可以先将数据存储与字典中:

novel = {
	'name': '太初',
    'author': '高楼大夏',
    'form': '连载',
    'type': '玄幻'
}

然后调用 db_collection 的 insert_one() 方法将新文档插入集合 hot 中:

result = db_collection.insert_one(novel)
print(result)
print(result.inserted_id)

在添加文档时,如果没有显式指明,该方法会为每个文档添加一个 ObjectId 类型的 _id 字段,作为文档的唯一识别号。可以使用 result 的 inserted_id 属性来获取 _id 的值。

然后运行结果就可以发现,result 是一个 InsertOneResult 类型的对象。

插入与条件匹配的所有文档。

还可以使用 insert_many() 方法,一次插入多个文档:

novel1 = {
	'name': '太初1',
    'author': '高楼大夏',
    'form': '连载',
    'type': '玄幻'
}
novel2 = {
	'name': '太初2',
    'author': '高楼大夏',
    'form': '连载',
    'type': '玄幻'
}
result = db_collection.insert_many([novel1, novel2])
print(result)

方法 insert_many() 返回一个 InsertOneResult 对象。可以调用该对象的 inserted_ids 属性来获取插入的所有文档的 _id 列表。

注意事项。
  1. 创建集合:如果当前的集合不存在,插入操作会自动创建集合。
  2. _id 字段:在 MongoDB 中,存储在集合中的每个文档都需要一个唯一的 _id 字段作为主键。如果插入的文档省略了该 _id 字段,MongoDB 驱动程序会自动为该字段生成 ObjectId 类型的 _id。
  3. 原子性:MongoDB 中的所有写入操作都是单个文档级别的原子操作。

查询文档

可以使用 find_one() 或 find() 方法查询集合中的文档记录。find_one() 方法返回单个文档记录,而 find() 方法则返回一个游标对象,用于查询多个文档记录。下面来了解查询文档的各种用法:

查询与条件匹配的单个文档

要查询集合 hot 中,name 为 “帝国的崛起” 的文档记录,可以将查询条件存储与字典中,并作为参数传递给 find_one() 方法:

result = db_collection.find_one({'name': '帝国的崛起'})
print(result)

该操作对应以下 SQL 语句:

SELECT * FROM hot WHERE name = "帝国的崛起" LIMIT 0, 1

运行,就会以字典的格式输出结果了。

注:find_one() 方法仅返回符合条件的第一个文档,如果没有符合的文档,则返回 None。

查询所有文档

如果要查询集合中所有的文档,可将空字典作为参数传递给 find() 方法:

cursor = db_collection.find({})

该操作对应以下 SQL 语句:

SELECT * FROM hot
查询与条件匹配的所有文档

查询集合 hot 中,所有 type 为 “历史” 的文档记录:

cursor = db_collection.find({'type': '历史'})
print(cursor)  # 这个应该是列表了
for one in cursor:  # 遍历所有文档
	print(one)

find() 方法返回一个 Cursor 类型的对象 cursor,它可以通过 for 循环遍历所有取得的结果。

该操作对应与以下 SQL 语句:

SELECT * FROM hot WHERE type = "历史"

更新文档

Redis 数据库



其他

1、将所使用的包导出来

  • 注意:会将编译器所有的包都导出来,所以在创建项目的时候,尽量使用虚拟环境。
# 安装模块
pip install pipreqs
# 切换到项目的目录里,然后执行命令,就会在项目的目录下新建一个requriements文本,里面会包含所有的包。
pip freeze > requriements.txt
# 安装文件里所有的包
pip install -r requriements.txt
# 使用清华源去下载(待测试,理论上可以执行)
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requriements.txt

2、关闭检查 robots

# settings.py
# Obey robots.txt rules
ROBOTSTXT_OBEY = True

3、使用 py 文件去执行 scrapy

运行爬虫时每次都要打开命令行输入是不是很麻烦?

scrapy 提供了一个 cmdline 库,可以非常方便的运行爬虫程序。

首先在项目的根目录新建一个.py文件,我这里是名字起个 start.py,然后写以下代码:

# start.py
from scrapy import cmdline

cmdline.execute("scrapy crawl hot".split())  # 中间的参数就是命令了

更多例子

1、抓取小说存储到csv

import scrapy
from scrapy import Request


class QuotesSpider(scrapy.Spider):
    name = "ww"

    qidian_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                                    'AppleWebKit/537.36 (KHTML, like Gecko) '
                                    'Chrome/79.0.3945.88 Safari/537.36'}

    # 获取初始化 Request
    def start_requests(self):  # 有了这个函数就不需要有 start_urls 属性去设置 URL 了
        url = 'https://www.shuwulou.com/shu/48045.html'
        # 生成请求对象,设置 url、headers、callback
        yield Request(url, headers=self.qidian_headers, callback=self.parse)

    def parse(self, response, **kwargs):
        cssList = '.zjlist dd'
        list_title = response.css(cssList)
        for l in list_title:
            newURL = l.css('a::attr(href)').get()
            newURL = 'https://www.shuwulou.com' + newURL
            title = l.css('a::text').get()
            # 将需要存储的数据,通过 meta参数传递给最后一个函数,只有最后一个函数才能将所有数据存储完
            yield response.follow(newURL, callback=self.context, meta={'title': title, 'newURL': newURL})

    def context(self, response):
        title = response.request.meta['title']  # 接收上一个函数传递过来的数据
        newURL = response.request.meta['newURL']  # 接收上一个函数传递过来的数据
        title1 = response.css('h1::text').get()
        zuozhe = response.css('.read-titlelinke span::text').get()
        yield {
            '标题': title,
            '链接': newURL,
            '书内标题': title1,
            '作者': zuozhe
        }

2、爬取乐有家全国的租房信息

# item.py
import scrapy

class LeyoujiaItem(scrapy.Item):
    title = scrapy.Field()  # 标题
    HouseType = scrapy.Field()  # 户型
    orientation = scrapy.Field()  # 朝向
    built_up_area = scrapy.Field()  # 建筑面积
    Inside_area = scrapy.Field()  # 套内面积
    renovation = scrapy.Field()  # 装修程度
    floor = scrapy.Field()  # 楼层
    HouseYear = scrapy.Field()  # 房子年份
    residential_quarters = scrapy.Field()  # 住宅区
    Lot = scrapy.Field()  # 地段
    other = scrapy.Field()  # 其他
    rent = scrapy.Field()  # 租金
    Payment_mode = scrapy.Field()  # 付款模式;压几付几
    Rental_form = scrapy.Field()  # 租房形式;整组/合租
# spiders/lyj.py
import scrapy
from scrapy import Request
from leyoujia.items import LeyoujiaItem

class LyjSpider(scrapy.Spider):
    name = 'lyj'
    # allowed_domains = ['shenzhen.leyoujia.com/zf/']
    url = 'https://guangzhou.leyoujia.com'

    head = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/86.0.4240.183 Safari/537.36'}

    def start_requests(self):
        # 初始访问
        yield Request(self.url, headers=self.head, callback=self.parse_url)

    def parse_url(self, response, **kwargs):
        # 获取全国的链接,每个地方的链接都不一样
        chengshi = response.css('.city-opts a::attr(href)').getall()
        for i in chengshi:
            yield Request(i+'/zf/', headers=self.head, callback=self.parse, meta={'purl': i})  # 将地方的链接传递给 parse 函数,方便翻页使用

    def parse(self, response, **kwargs):
        purl = response.request.meta['purl']
        homeList = response.xpath('//li[@class="item clearfix"]')
        for home in homeList:
            item = LeyoujiaItem()
            item['title'] = home.xpath('div[@class="text"]/p[1]/a/text()').getall()
            item['HouseType'] = home.xpath('div[@class="text"]/p[2]/span[1]/text()').getall()
            item['orientation'] = home.xpath('div[@class="text"]/p[2]/span[2]/text()').getall()
            item['built_up_area'] = home.xpath('div[@class="text"]/p[2]/span[3]/text()').getall()
            item['Inside_area'] = home.xpath('div[@class="text"]/p[2]/span[4]/text()').getall()

            item['renovation'] = home.xpath('div[@class="text"]/p[3]/span[1]/text()').getall()
            item['floor'] = home.xpath('div[@class="text"]/p[3]/span[2]/text()').getall()
            item['HouseYear'] = home.xpath('div[@class="text"]/p[3]/span[3]/text()').getall()

            item['residential_quarters'] = home.xpath('div[@class="text"]/p[4]/span[1]/a/text()').getall()
            item['Lot'] = home.xpath('div[@class="text"]/p[4]/span[2]/a/text()').getall()

            item['other'] = home.xpath('div[@class="text"]/p[5]/span/text()').getall()

            item['rent'] = home.xpath('div[@class="price"]/p[1]/span/text()').getall()
            qita = home.xpath('div[@class="price"]/p[2]/text()').get().split('|')
            item['Payment_mode'] = qita[1].strip()
            item['Rental_form'] = qita[0].strip()
            yield item

        new_page = response.xpath("//a[text()='下一页 ']")
        if new_page is not None:
            urls = purl + new_page.attrib['href']  # 获取 href 属性
            yield scrapy.Request(urls, headers=self.head, callback=self.parse, meta={'purl': purl})
            # 如果需要回调到 parse 这个函数,那么 purl 属性也需要传递回给自身,否则下一页将会获取不到 purl 了
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值