Python爬虫之Scrapy框架基础入门

Scrapy 是一个用于Python的开源网络爬虫框架,它为编写网络爬虫来抓取网站数据并提取结构化信息提供了一种高效的方法。Scrapy可以用于各种目的的数据抓取,如数据挖掘、监控和自动化测试等。

【1】安装

pip install scrapy

安装成功如下所示:
在这里插入图片描述
如果安装过程出错,可以参考下面步骤解决:

# (1) pip install scrapy
# (2) 报错1: building 'twisted.test.raiser' extension
#              error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++
#              Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
#     解决1
#       http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
#       Twisted‑20.3.0‑cp37‑cp37m‑win_amd64.whl
#       cp是你的python版本
#       amd是你的操作系统的版本
#       下载完成之后 使用pip install twisted的路径  安装
#       切记安装完twisted 再次安装scrapy

# (3) 报错2  提示python -m pip install --upgrade pip
#      解决2   运行python -m pip install --upgrade pip

# (4) 报错3   win32的错误
#      解决3   pip install pypiwin32

# (5) anaconda

【2】基础入门

scrapy项目的结构

项目名字
    项目名字
        spiders文件夹 (存储的是爬虫文件)
            init
            自定义的爬虫文件    核心功能文件  ****************
        init
        items        定义数据结构的地方 爬取的数据都包含哪些
        middleware   中间件    代理
        pipelines    管道   用来处理下载的数据
        settings     配置文件    robots协议  ua定义等

在这里插入图片描述

创建Scrapy项目

创建一个新的Scrapy项目,你可以在命令行中输入以下命令:

scrapy startproject myproject

这将在当前目录下创建一个名为myproject的新目录,其中包含了Scrapy项目的结构。

scrapy startproject jane

在这里插入图片描述

定义Item

在Scrapy中,Item是被用来保存抓取到的数据的容器。你可以定义自己的Item类,类似于Python字典,但是提供了额外保护机制和便利方法。Item通常定义在items.py文件中。

class ScrapyDangdangItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 通俗的说就是你要下载的数据都有什么
    # 图片
    src = scrapy.Field()
    # 名字
    name = scrapy.Field()
    # 价格
    price = scrapy.Field()
    # 详情URL
    detail_url = scrapy.Field()

编写Spider

创建爬虫文件, 要在spiders文件夹中去创建爬虫文件: cd 项目的名字\项目的名字\spiders

scrapy genspider 爬虫文件的名字  要爬取网页

# 示例如下
scrapy genspider baidu  http://www.baidu.com

一般情况下不需要添加http协议 因为start_urls的值是根据allowed_domains修改的 所以添加了http的话 那么start_urls就需要我们手动去修改了

Spiders是定义如何抓取某个(或某些)网站的类。每个Spider负责处理一个特定的网站,或者一组相关的网页。Spiders通常位于spiders目录下,并且以.py文件的形式存在。

class BaiduSpider(scrapy.Spider):
    name = "baidu"
    allowed_domains = ["www.baidu.com"]
    start_urls = ["http://www.baidu.com"]

# 是执行了start_urls之后 执行的方法   方法中的response 就是返回的那个对象
    # 相当于 response = urllib.request.urlopen()
    #       response  = requests.get()
    def parse(self, response):
    	# 可以直接使用xpath或BS4
     	span = response.xpath('//div[@id="filter"]/div[@class="tabs"]/a/span')[0]
        print('=======================')
        print(span.extract())

response的属性和方法

response.text   获取的是响应的字符串
response.body   获取的是二进制数据
response.xpath  可以直接是xpath方法来解析response中的内容
response.extract()   提取seletor对象的data属性值
response.extract_first() 提取的seletor列表的第一个数据

管道 (Pipeline)

管道是用来处理由Spider抓取并返回的Items的地方。你可以在pipelines.py中定义如何处理这些Items,比如清洗、验证数据或者将它们存储到数据库中。

设置 (Settings)

Scrapy的行为可以通过修改settings.py文件来定制,例如设置下载延迟、启用/禁用中间件、更改用户代理等。

运行 Spider

最后,你可以通过命令行运行你的Spider:

scrapy crawl baidu

【3】管道的使用

pipelines管道就是用来处理数据的,如数据清洗、处理、校验、修正以及存储。

如果想使用管道的话 那么就必须在settings中开启管道,可以定义多个管道通过优先级数值来决定执行次序。

from itemadapter import ItemAdapter
class ScrapyDangdangPipeline:
    # 在爬虫文件开始的之前就执行的一个方法
    def open_spider(self,spider):
        self.fp = open('book.json','w',encoding='utf-8')

    # item就是yield后面的book对象
    def process_item(self, item, spider):
        # 以下这种模式不推荐  因为每传递过来一个对象 那么就打开一次文件  对文件的操作过于频繁

        # # (1) write方法必须要写一个字符串 而不能是其他的对象
        # # (2) w模式 会每一个对象都打开一次文件 覆盖之前的内容
        # with open('book.json','a',encoding='utf-8')as fp:
        #     fp.write(str(item))

        self.fp.write(str(item))


        return item

    # 在爬虫文件执行完之后  执行的方法
    def close_spider(self,spider):
        self.fp.close()

import urllib.request

# 多条管道开启
#    (1) 定义管道类
#   (2) 在settings中开启管道
# 'scrapy_dangdang_.pipelines.DangDangDownloadPipeline':301
class DangDangDownloadPipeline:
    def process_item(self, item, spider):

        url = 'http:' + item.get('src')
        filename = './books/' + item.get('name') + '.jpg'

        urllib.request.urlretrieve(url = url, filename= filename)

        return item

settings.py中开启管道:

ITEM_PIPELINES = {
   #  管道可以有很多个  那么管道是有优先级的  优先级的范围是1到1000   值越小优先级越高
   'scrapy_dangdang_.pipelines.ScrapyDangdangPipeline': 300,

#    DangDangDownloadPipeline
   'scrapy_dangdang_.pipelines.DangDangDownloadPipeline':301
}

【4】Scrapy中yield的使用

在 Scrapy 中,yield 有着特别重要的作用,尤其是在 Spider 类中。Scrapy 使用 yield 来返回请求(Request)和项目(Item),而不需要将它们全部加载到内存中。这使得 Scrapy 可以高效地处理大量的页面和数据。

在 Scrapy 中,yield 主要用于以下两个场景:

1. 返回 Request 对象

当您需要从一个页面抓取多个链接并发送请求时,可以使用 yield 来逐个返回 Request 对象。Scrapy 会自动处理这些请求,并将响应传递给指定的回调函数。

class DangSpider(scrapy.Spider):
    name = 'dang'
    start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']

    def parse(self, response):
        li_list = response.xpath('//ul[@id="component_59"]/li')

        for li in li_list:
            detail_url = 'http:' + li.xpath('./a/@href').get()
            if detail_url:
                yield scrapy.Request(detail_url, callback=self.parse_detail)

    def parse_detail(self, response):
        review_count = response.xpath('//a[@id="comm_num_down"]/@dd_name').get()
        if review_count is None:
            review_count = "评论数未找到"
        else:
            review_count = review_count.strip()

        self.logger.info(f"当前图书评论数为: {review_count}")

在这个例子中,parse 方法会遍历列表页中的每个书籍链接,并使用 yield 逐个返回 Request 对象。Scrapy 会依次处理这些请求,并将详情页的响应传递给 parse_detail 方法。

2. 返回 Item 对象

当您从页面中提取数据并构建 Item 时,可以使用 yieldItem 返回给 Scrapy 的管道进行进一步处理(如保存到数据库、导出为文件等)。

class DangSpider(scrapy.Spider):
    name = 'dang'
    start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']

    def parse(self, response):
        li_list = response.xpath('//ul[@id="component_59"]/li')

        for li in li_list:
            src = li.xpath('.//img/@data-original').get() or li.xpath('.//img/@src').get()
            name = li.xpath('.//img/@alt').get()
            price = li.xpath('.//p[@class="price"]/span[1]/text()').get()
            detail_url = 'http:' + li.xpath('./a/@href').get()

            if not src or not name or not price or not detail_url:
                self.logger.warning("缺少关键信息,跳过此条目")
                continue

            book_info = {
                'src': src,
                'name': name,
                'price': price,
                'detail_url': detail_url
            }

            yield scrapy.Request(detail_url, callback=self.parse_detail, cb_kwargs={'book_info': book_info})

    def parse_detail(self, response, book_info):
        review_count = response.xpath('//a[@id="comm_num_down"]/@dd_name').get()
        if review_count is None:
            review_count = "评论数未找到"
        else:
            review_count = review_count.strip()

        book_info['review_count'] = review_count
        book_item = ScrapyDangdangItem(**book_info)
        self.logger.info(f"抓取到完整图书信息: {book_item}")

        yield book_item

在这个例子中,parse_detail 方法会从详情页提取评论数,并将其添加到 book_info 字典中。然后,它会创建一个 ScrapyDangdangItem 实例,并使用 yield 将其返回给 Scrapy 的管道。

【5】parse方法之间如何传参?

比如,需要先获取分类列表,然后获取每一个详情,最后整合获取得到book信息。

在 Scrapy 中,通常情况下,您会希望将从详情页获取的数据(如评论数)与列表页获取的数据(如书名、价格等)结合起来,形成一个完整的 Item。为了实现这一点,您可以使用 Requestmeta 参数来传递数据,或者使用 cb_kwargs 参数(Scrapy 1.7+)来传递关键字参数。

方法 1: 使用 meta 参数

meta 参数允许您在请求之间传递数据。您可以在 parse 方法中将书籍的基本信息(如书名、价格、图片链接等)通过 meta 传递给 parse_detail 方法,然后在 parse_detail 中提取评论数并返回一个完整的 Item。

修改后的代码:

import scrapy
from ..items import ScrapyDangdangItem  # 确保这里正确导入了Item

class DangSpider(scrapy.Spider):
    name = 'dang'
    allowed_domains = ['dangdang.com']  # 放宽域名限制
    start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']

    base_url = 'http://category.dangdang.com/pg'
    page = 1

    def parse(self, response):
        li_list = response.xpath('//ul[@id="component_59"]/li')

        for li in li_list:
            src = li.xpath('.//img/@data-original').get() or li.xpath('.//img/@src').get()
            name = li.xpath('.//img/@alt').get()
            price = li.xpath('.//p[@class="price"]/span[1]/text()').get()

            detail_url = 'http:' + li.xpath('./a/@href').get()

            if not src or not name or not price or not detail_url:
                self.logger.warning("缺少关键信息,跳过此条目")
                continue

            # 构建基础的图书信息
            book_info = {
                'src': src,
                'name': name,
                'price': price,
                'detail_url': detail_url
            }

            # 发送详情页请求,并通过 meta 传递图书信息
            yield scrapy.Request(detail_url, callback=self.parse_detail, meta={'book_info': book_info})

    def parse_detail(self, response):
        # 从 meta 中获取图书信息
        book_info = response.meta['book_info']

        # 提取评论数
        review_count = response.xpath('//a[@id="comm_num_down"]/@dd_name').get()
        if review_count is None:
            review_count = "评论数未找到"
        else:
            review_count = review_count.strip()

        # 将评论数添加到图书信息中
        book_info['review_count'] = review_count

        # 创建并返回完整的 Item
        book_item = ScrapyDangdangItem(**book_info)
        self.logger.info(f"抓取到完整图书信息: {book_item}")

        yield book_item

方法 2: 使用 cb_kwargs 参数 (Scrapy 1.7+)

cb_kwargs 是 Scrapy 1.7 版本引入的一个新特性,它允许您直接在 Request 中传递关键字参数,而不需要通过 meta。这种方式更加直观和简洁。

修改后的代码:

import scrapy
from ..items import ScrapyDangdangItem  # 确保这里正确导入了Item

class DangSpider(scrapy.Spider):
    name = 'dang'
    allowed_domains = ['dangdang.com']  # 放宽域名限制
    start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']

    base_url = 'http://category.dangdang.com/pg'
    page = 1

    def parse(self, response):
        li_list = response.xpath('//ul[@id="component_59"]/li')

        for li in li_list:
            src = li.xpath('.//img/@data-original').get() or li.xpath('.//img/@src').get()
            name = li.xpath('.//img/@alt').get()
            price = li.xpath('.//p[@class="price"]/span[1]/text()').get()

            detail_url = 'http:' + li.xpath('./a/@href').get()

            if not src or not name or not price or not detail_url:
                self.logger.warning("缺少关键信息,跳过此条目")
                continue

            # 构建基础的图书信息
            book_info = {
                'src': src,
                'name': name,
                'price': price,
                'detail_url': detail_url
            }

            # 发送详情页请求,并通过 cb_kwargs 传递图书信息
            yield scrapy.Request(detail_url, callback=self.parse_detail, cb_kwargs={'book_info': book_info})

    def parse_detail(self, response, book_info):
        # 提取评论数
        review_count = response.xpath('//a[@id="comm_num_down"]/@dd_name').get()
        if review_count is None:
            review_count = "评论数未找到"
        else:
            review_count = review_count.strip()

        # 将评论数添加到图书信息中
        book_info['review_count'] = review_count

        # 创建并返回完整的 Item
        book_item = ScrapyDangdangItem(**book_info)
        self.logger.info(f"抓取到完整图书信息: {book_item}")

        yield book_item

关键点解释

  1. meta 参数

    • parse 方法中,我们构建了一个包含图书基本信息的字典 book_info
    • 使用 meta 参数将 book_info 传递给 parse_detail 方法。
    • parse_detail 中,通过 response.meta['book_info'] 获取传递过来的图书信息。
  2. cb_kwargs 参数

    • parse 方法中,我们同样构建了一个包含图书基本信息的字典 book_info
    • 使用 cb_kwargs 参数将 book_info 作为关键字参数传递给 parse_detail 方法。
    • parse_detail 中,直接通过函数参数 book_info 获取传递过来的图书信息。
  3. 合并数据

    • parse_detail 中,我们提取了评论数,并将其添加到 book_info 字典中。
    • 最后,我们创建了一个 ScrapyDangdangItem 实例,并将其返回给 Scrapy 的管道进行处理。

【6】管道中使用pymysql存储数据

from itemadapter import ItemAdapter


class ScrapyReadbookPipeline:

    def open_spider(self,spider):
        self.fp = open('book.json','w',encoding='utf-8')


    def process_item(self, item, spider):
        self.fp.write(str(item))
        return item

    def close_spider(self,spider):
        self.fp.close()

# 加载settings文件
from scrapy.utils.project import get_project_settings
import pymysql


class MysqlPipeline:

    def open_spider(self,spider):
        settings = get_project_settings()
        self.host = settings['DB_HOST']
        self.port =settings['DB_PORT']
        self.user =settings['DB_USER']
        self.password =settings['DB_PASSWROD']
        self.name =settings['DB_NAME']
        self.charset =settings['DB_CHARSET']

        self.connect()

    def connect(self):
        self.conn = pymysql.connect(
                            host=self.host,
                            port=self.port,
                            user=self.user,
                            password=self.password,
                            db=self.name,
                            charset=self.charset
        )

        self.cursor = self.conn.cursor()


    def process_item(self, item, spider):

        sql = 'insert into book(name,src) values("{}","{}")'.format(item['name'],item['src'])
        # 执行sql语句
        self.cursor.execute(sql)
        # 提交
        self.conn.commit()


        return item


    def close_spider(self,spider):
        self.cursor.close()
        self.conn.close()

数据库配置信息在settings.py中:

在这里插入图片描述

【7】日志级别与存储路径

settings.py中配置日志级别与路径即可:

# 指定日志的级别
# LOG_LEVEL='WARNING'

LOG_FILE = 'logdemo.log'

【8】POST请求

如下所示是GET请求:

yield scrapy.Request(url=detail_url, callback=self.parse_detail)

重写start_requests方法使用FormRequest发送POST请求

class TestpostSpider(scrapy.Spider):
    name = 'testpost'
    allowed_domains = ['https://fanyi.baidu.com/sug']
    # post请求 如果没有参数 那么这个请求将没有任何意义
    # 所以start_urls 也没有用了
    # parse方法也没有用了
    # start_urls = ['https://fanyi.baidu.com/sug/']
    #
    # def parse(self, response):
    #     pass

    def start_requests(self):
        url = 'https://fanyi.baidu.com/sug'

        data = {
            'kw': 'final'
        }

        yield scrapy.FormRequest(url=url,formdata=data,callback=self.parse_second)

    def parse_second(self,response):

        content = response.text
        obj = json.loads(content,encoding='utf-8')

        print(obj)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/936681.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【电子元器件】电感基础知识

本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时,也能帮助其他需要参考的朋友。如有谬误,欢迎大家进行指正。 一、 电感的基本工作原理 1. 电感的基本工作原理如下: (1) 当线圈中有电流通过时&#…

OpenGL ES详解——多个纹理实现混叠显示

目录 一、获取图片纹理数据 二、着色器编写 1. 顶点着色器 2. 片元着色器 三、绑定和绘制纹理 1. 绑定纹理 2. 绘制纹理 四、源码下载 一、获取图片纹理数据 获取图片纹理数据代码如下: //获取图片1纹理数据 mTextureId loadTexture(mContext, R.mipmap.…

C#,在 C# 语言中将 LaTeX 转换为 PNG 或 JPG 图像

在 C 语言中将 LaTeX 转换为 PNG 或 JPG 图像# 12月 28, 2021 2 分钟 法尔汉拉扎 在 C 语言中将 TeX 转换为 PNG JPG 图像# TeX 格式用于处理技术和科学文件。它通常用于交流或发布此类文档。在某些情况下,您可能需要将 TeX 文件渲染为 PNG 或 JPG 等图像…

顺序表(数据结构初阶)

文章目录 顺序表一:线性表1.1概念: 二:顺序表2.1概念与结构:2.2分类:2.2.1静态顺序表2.2.2动态顺序表 2.3动态顺序表的实现声明(初始化)检查空间容量尾插头插尾删头删查找指定位置之前插入数据指…

活动报名:Voice Agent 开发者分享会丨RTE Meetup

引入 voice agent 的口语学习应用 Speak 估值已达 10 亿美元 Voice Agent 开发者分享会 一同探索语音驱动的下一代人机交互界面,一场 voice agent builder 的小规模深度交流会。 RTE Meetup 迎来第六期!12 月 15 日(周日)上午&…

STM32 CubeMx HAL库 独立看门狗IWDG配置使用

看门狗这里我就不多介绍了,能搜到这篇文章说明你了解 总之就是一个单片机重启程序,设定好超时时间,在超时时间内没有喂狗,单片机就会复位 主要应用在单片机异常重启方面,比如程序跑飞(注意程序跑飞时你就…

pdb调试器详解

文章目录 1. 启动 pdb 调试器1.1 在代码中插入断点1.2 使用命令行直接调试脚本 2. 常用调试命令2.1 基本命令2.2 高级命令2.3 断点操作 3. 调试过程示例4. 调试技巧4.1 条件断点4.2 自动启用调试4.2.1 运行程序时指定 -m pdb4.2.2在代码中启用 pdb.post_mortem4.2.3 使用 sys.e…

(转,自阅,侵删)【LaTeX学习笔记】一文入门LaTeX(超详细)

【LaTeX学习笔记】一文入门LaTeX(超详细)-阿里云开发者社区LaTeX中主要分为导言区和正文区导言区通常用于定义文档的格式、语言等(全局设置)。常用的LaTex命令主要有\documentclass,\usepackage等。下面分别对几个常用…

MongoDB-ObjectID 生成器

前言 MongoDB中一个非常关键的概念就是 ObjectID,它是 MongoDB 中每个文档的默认唯一标识符。了解 ObjectID 的生成机制不仅有助于开发人员优化数据库性能,还能帮助更好地理解 MongoDB 的设计理念。 什么是 MongoDB ObjectID? 在 MongoDB …

MFC学习笔记专栏开篇语

MFC,是一个英文简写,全称为 Microsoft Foundation Class Library,中文翻译为微软基础类库。它是微软开发的一套C类库,是面向对象的函数库。 微软开发它,是为了给程序员提供方便,减少程序员的工作量。如果没…

GPTcelltype——scRNA-seq注释

#安装包 install.packages("openai") remotes::install_github("Winnie09/GPTCelltype") #填写API Sys.setenv(OPENAI_API_KEY your_openai_API_key) #加载包 #Load packages library(GPTCelltype) library(openai) #准备文件 #Assume you have already r…

WebRTC服务质量(03)- RTCP协议

一、前言: RTCP(RTP Control Protocol)是一种控制协议,与RTP(Real-time Transport Protocol)一起用于实时通信中的控制和反馈。RTCP负责监控和调节实时媒体流。通过不断交换RTCP信息,WebRTC应用…

用户认证系统登录界面

下面是使用HTML和JavaScript实现的一个中文版登录界面&#xff0c;包含登录、注册和修改密码功能。注册成功后会显示提示信息&#xff0c;在登录成功后进入一个大大的欢迎页面。 1.代码展示 <!DOCTYPE html> <html lang"zh-CN"> <head><meta …

uniapp中vuex(全局共享)的应用

一、Vuex概述 1.1 官方解释 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。 它采用集中式存储管理 应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化 - Vuex 也集成到 Vue 的官方调试工具 devtools extension&#xff0c;提供了诸…

不能通过 ip 直接访问 共享盘 解决方法

from base_config.config import OpenSMB, SMB import os, time, calendar, requests, decimal, platform, fs.smbfsinfo_dict SMB.EPDI_dict info_dict[host] (FS03,10.6.12.182) info_dict[direct_tcp] True# smb OpenSMB(info_dict)print(ok)# 根据 ip 查询电脑名 impor…

JavaEE初阶——多线程(线程安全-锁)

复习上节内容&#xff08;部分-掌握程度不够的&#xff09; 加锁&#xff0c;解决线程安全问题。 synchronized关键字&#xff0c;对锁对象进行加锁。 锁对象&#xff0c;可以是随便一个Object对象&#xff08;或者其子类的对象&#xff09;&#xff0c;需要关注的是&#xff…

day2 数据结构 结构体的应用

思维导图 小练习&#xff1a; 定义一个数组&#xff0c;用来存放从终端输入的5个学生的信息【学生的信息包含学生的姓名、年纪、性别、成绩】 1>封装函数 录入5个学生信息 2>封装函数 显示学生信息 3>封装函数 删除第几个学生信息&#xff0c;删除后调用显示学…

五、网络层:控制平面,《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》

目录 一、导论 二、路由选择算法 2.1 路由&#xff08;route&#xff09;的概念 2.2 网络的图抽象 2.2.1 边和路由的代价 2.2.2 最优化原则 2.3 路由的原则 2.4 路由选择算法的分类 2.5 link state 算法 2.5.1 LS路由工作过程 2.5.2 链路状态路由选择&#xff08;lin…

内网是如何访问到互联网(H3C源NAT)

H3C设备NAPT配置 直接打开29篇的拓扑&#xff0c;之前都配置好了 「模拟器、工具合集」复制整段内容 链接&#xff1a;https://docs.qq.com/sheet/DV0xxTmFDRFVoY1dQ?tab7ulgil 现在是出口路由器可以直接访问61.128.1.1&#xff0c;下面的终端访问不了&#xff0c;需要做NAPT源…

生产者-消费者模型

目录 生产者-消费者模型介绍 生产者-消费者模型优点 生产者-消费者之间的关系 基于阻塞队列实现生产者-消费者模型 基于环形队列实现生产者-消费者模型 生产者-消费者模型介绍 ● 计算机中的生产者和消费者本质都是线程/进程 ● 生产者和消费者不直接通讯&#xff0c;而是…