本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本文介绍如何使用Python的Scrapy框架爬取房天下网站的房产信息,并将抓取的数据存储到MySQL数据库中。Scrapy是一个功能强大的网络爬虫框架,MySQL则用于结构化数据存储。通过本项目实战,读者可掌握爬虫项目搭建、网页解析、模拟登录、数据存储等关键技术,适用于数据分析、数据采集等场景。项目还涉及反爬应对、数据清洗和异常处理等内容,具备完整的爬虫开发流程。
使用python的scrapy框架获取房天下家族信息并存入mysql数据库

1. Scrapy框架环境搭建与项目初始化

在进行网络爬虫开发之前,搭建一个稳定且高效的Scrapy开发环境是至关重要的。本章将从Python环境的配置入手,逐步引导读者完成Scrapy框架的安装、项目初始化及基础结构解析。通过本章学习,您将掌握创建Scrapy项目的完整流程,并理解各目录文件的作用,为后续爬取房天下网站数据奠定坚实基础。

1.1 Python环境准备

在安装Scrapy之前,确保系统中已正确安装Python环境。推荐使用Python 3.8及以上版本,以获得更好的兼容性和性能支持。

操作步骤:

  1. 下载安装Python
    访问 Python官网 ,根据操作系统选择对应版本进行安装。

  2. 验证Python安装
    打开终端(或命令行工具),输入以下命令查看Python版本:

bash python --version # 或 python3 --version

  1. 安装pip包管理器 (如未内置)
    使用以下命令确保pip已安装:

bash python -m ensurepip --upgrade

环境建议:

  • 推荐使用虚拟环境(如 venv conda )隔离项目依赖,提升开发与部署效率。

2. 房天下网站结构分析与页面解析

在构建一个高效的网络爬虫之前,首先需要对目标网站的结构进行深入分析。房天下作为中国知名的房地产信息平台,其网站结构复杂,页面内容动态加载频繁,对于爬虫开发者来说具有一定的挑战性。本章将从网站结构概述、页面解析技术以及内容抓取策略三个方面入手,全面解析房天下的页面结构与数据获取方式,为后续的数据提取与处理打下坚实基础。

2.1 网站结构概述

2.1.1 房天下网站页面布局与URL结构

房天下的网站主要分为多个功能模块,包括新房、二手房、租房、资讯等。每个模块的URL结构都有一定的规律,理解这些结构有助于我们构建高效的爬虫逻辑。

以新房频道为例,其主要页面结构如下:

页面类型 示例URL 结构说明
首页 https://newhouse.fang.com/ 新房首页,展示各城市新房入口
城市列表页 https://newhouse.fang.com/house-s/ 包含全国主要城市的链接
城市新房列表页 https://bj.newhouse.fang.com/house-s/ 北京地区新房列表
楼盘详情页 https://bj.newhouse.fang.com/house/1234567890/ 具体楼盘的详细信息

通过观察可以发现,URL结构通常遵循以下模式:

  • 域名结构: {城市缩写}.newhouse.fang.com
  • 页面路径: /house-s/ 表示列表页, /house/{楼盘ID}/ 表示详情页

这种结构化的设计便于我们通过程序自动生成目标URL,从而实现自动化的数据抓取。

2.1.2 静态页面与动态加载内容的识别

在进行网页抓取时,必须识别页面中哪些内容是静态加载的,哪些是通过JavaScript动态加载的。房天下的页面结构中,部分数据是通过AJAX请求动态获取的,这对我们直接使用Scrapy进行抓取提出了挑战。

例如,在楼盘详情页中,部分楼盘参数(如价格、面积等)是通过异步请求获取的,如下图所示的请求:

graph TD
    A[用户访问详情页] --> B[浏览器发起初始HTML请求]
    B --> C[服务器返回基础HTML]
    C --> D[浏览器解析HTML并执行JS]
    D --> E[发起AJAX请求获取楼盘参数]
    E --> F[服务器返回JSON数据]
    F --> G[渲染最终页面]

面对这种情况,我们可以采用以下策略:

  1. 直接分析AJAX请求 :使用浏览器的开发者工具(F12)查看Network面板,找到数据源接口,模拟请求获取JSON数据。
  2. 使用Selenium或Playwright :如果动态内容较多且难以通过接口获取,可以借助无头浏览器工具来渲染页面。

下面是一个使用Scrapy抓取静态页面的简单示例代码:

import scrapy

class FangSpider(scrapy.Spider):
    name = 'fang_spider'
    start_urls = [
        'https://bj.newhouse.fang.com/house-s/'
    ]

    def parse(self, response):
        # 提取楼盘名称列表
        for house in response.css('div.nlc_details'):
            yield {
                'name': house.css('div.houses_text a::text').get(),
                'url': house.css('div.houses_text a::attr(href)').get()
            }

        # 提取下一页链接并继续爬取
        next_page = response.css('a.next::attr(href)').get()
        if next_page:
            yield response.follow(next_page, self.parse)

代码逐行解析:
1. 导入scrapy模块。
2. 定义一个Spider类,设置起始URL。
3. parse 方法用于解析响应内容。
4. 使用CSS选择器提取楼盘名称和链接。
5. 提取下一页链接并使用 response.follow 进行递归爬取。

2.2 页面解析技术

2.2.1 使用浏览器开发者工具分析HTML结构

在进行页面解析之前,熟练掌握浏览器开发者工具的使用是必不可少的。Chrome浏览器的开发者工具提供了强大的DOM查看与元素定位功能,帮助我们快速找到目标数据所在的HTML节点。

以房天下楼盘列表页为例:

  1. 打开页面并按下F12,进入开发者工具。
  2. 点击“元素”标签,使用鼠标选择器工具点击页面中的楼盘名称。
  3. 在DOM结构中定位到对应的 <a> 标签。
  4. 查看其父级节点结构,确认CSS路径。

例如,楼盘名称所在的HTML结构可能如下:

<div class="nlc_details">
    <div class="houses_text">
        <a href="/house/1234567890/" target="_blank">北京·万科新里程</a>
    </div>
</div>

对应的CSS选择器为: div.nlc_details div.houses_text a::text

2.2.2 XPath与CSS选择器的初步使用

Scrapy支持XPath和CSS选择器两种方式来提取页面数据。两者各有优势,XPath更擅长处理复杂的层级结构,而CSS选择器则更简洁易读。

CSS选择器示例:
# 提取楼盘名称
response.css('div.nlc_details div.houses_text a::text').get()

# 提取所有楼盘链接
response.css('div.nlc_details div.houses_text a::attr(href)').getall()
XPath示例:
# 提取楼盘名称
response.xpath('//div[@class="nlc_details"]//div[@class="houses_text"]/a/text()').get()

# 提取所有楼盘链接
response.xpath('//div[@class="nlc_details"]//div[@class="houses_text"]/a/@href').getall()

对比说明:
- CSS选择器语法更简洁,适合初学者。
- XPath支持更复杂的路径表达式,如 //div[contains(text(), '万科')]
- 推荐在Scrapy中优先使用CSS选择器,复杂结构再使用XPath辅助。

下面是一个结合CSS与XPath的混合使用示例:

def parse(self, response):
    for item in response.css('div.nlc_details'):
        name = item.css('div.houses_text a::text').get()
        link = item.css('div.houses_text a::attr(href)').get()
        price = item.xpath('.//div[@class="price_average"]/text()').get()
        yield {
            'name': name,
            'link': link,
            'price': price.strip() if price else None
        }

代码说明:
- item.css(...) 用于提取当前div下的子元素。
- .xpath(...) 使用 . 开头表示在当前节点下继续查找,避免重复解析全文档。

2.3 页面内容的抓取策略

2.3.1 列表页与详情页的区分与提取

在房天下的网站结构中,列表页通常展示多个楼盘的基本信息,而详情页则包含详细参数。抓取策略通常为“列表页抓取链接,详情页抓取数据”。

列表页抓取示例:
def parse_list(self, response):
    # 提取所有楼盘详情页链接
    links = response.css('div.nlc_details div.houses_text a::attr(href)').getall()
    for link in links:
        yield response.follow(link, callback=self.parse_detail)
详情页抓取示例:
def parse_detail(self, response):
    yield {
        'name': response.css('h1.title::text').get(),
        'price': response.css('span.price::text').get(),
        'area': response.css('div.area span::text').get(),
        'developer': response.xpath('//div[@class="developer"]/text()').get()
    }

策略说明:
- 使用 response.follow 将链接加入请求队列。
- callback 指定回调函数,分别处理列表与详情页。
- 避免在同一个函数中处理多个页面类型,提升代码可维护性。

2.3.2 多级页面跳转的处理方式

某些页面结构中存在多级跳转,例如:列表页 → 楼盘页 → 项目页 → 参数页。这种情况下,需要递归调用 response.follow ,逐级抓取。

示例代码:
def parse_list(self, response):
    for link in response.css('div.list a::attr(href)').getall():
        yield response.follow(link, callback=self.parse_project)

def parse_project(self, response):
    detail_link = response.css('div.project a.detail::attr(href)').get()
    if detail_link:
        yield response.follow(detail_link, callback=self.parse_detail)

def parse_detail(self, response):
    yield {
        'name': response.css('h1::text').get(),
        'price': response.css('span.price::text').get(),
        'address': response.xpath('//div[@id="address"]/text()').get()
    }

执行逻辑说明:
- parse_list 处理一级页面,提取项目页链接。
- parse_project 处理二级页面,提取详情页链接。
- parse_detail 最终提取详细数据。
- 每次跳转都使用 response.follow ,保证请求队列正确调度。

状态保持与数据传递:

在多级跳转中,有时需要在页面之间传递中间数据。可以使用 meta 参数实现:

yield response.follow(detail_link, callback=self.parse_detail, meta={'project_name': project_name})

在回调函数中读取:

def parse_detail(self, response):
    project_name = response.meta.get('project_name')
    ...

本章从网站结构分析入手,详细讲解了房天下网站的页面布局、URL结构、静态与动态内容识别,并深入介绍了使用Scrapy进行页面解析的技术手段,包括CSS选择器与XPath的使用技巧。最后,针对多级页面跳转的抓取策略进行了实践演示,为后续章节的数据提取与处理奠定了坚实基础。

3. 使用ItemLoader与XPath/CSS选择器提取数据

在数据抓取过程中,提取结构化数据是整个流程中最关键的一步。Scrapy 提供了 ItemLoader 工具来协助我们以一种更清晰、更可维护的方式组织字段提取逻辑。结合 XPath CSS选择器 的强大定位能力,我们可以实现高效、精准的数据提取。

本章将深入探讨 ItemLoader 的使用方式、XPath 与 CSS 选择器的实战应用,并进一步讨论数据提取过程中的优化策略,包括字段清洗、去重、标准化等关键处理手段。

3.1 ItemLoader的使用方法

ItemLoader 是 Scrapy 提供的一个封装类,用于构建 Item 实例。它支持链式调用,使得字段提取逻辑更清晰,也便于后续的字段处理(如数据清洗、格式转换等)。

3.1.1 ItemLoader的初始化与字段映射

在 Scrapy 爬虫中,通常通过如下方式创建 ItemLoader 实例:

from scrapy.loader import ItemLoader
from myproject.items import HouseInfoItem

def parse(self, response):
    loader = ItemLoader(item=HouseInfoItem(), response=response)
    loader.add_xpath('name', '//h1[@class="house-title"]/text()')
    loader.add_css('price', '.price span::text')
    loader.add_value('url', response.url)
    yield loader.load_item()

逐行解释
- 第1~2行:导入 ItemLoader 和自定义的 HouseInfoItem
- 第4行:在 parse() 方法中,传入 response 创建 ItemLoader 实例。
- 第5行:使用 add_xpath() 方法将字段 name 映射到 XPath 表达式。
- 第6行:使用 add_css() 方法提取价格字段。
- 第7行:使用 add_value() 方法添加静态值(如当前 URL)。
- 第8行:调用 load_item() 返回构建好的 Item 对象。

这种方式将字段提取逻辑集中在一个对象中,避免了在 Spider 中手动构造字典的混乱。

3.1.2 使用Input Processor与Output Processor处理原始数据

Scrapy 提供了输入处理器(Input Processor)和输出处理器(Output Processor)来处理字段值。默认情况下, MapCompose 作为输入处理器, TakeFirst 作为输出处理器。

例如,我们可以在 ItemLoader 中自定义字段的处理流程:

from scrapy.loader.processors import MapCompose, TakeFirst
from w3lib.html import remove_tags

class HouseInfoLoader(ItemLoader):
    default_output_processor = TakeFirst()
    name_in = MapCompose(remove_tags, str.strip)
    price_in = MapCompose(str.strip, lambda x: x.replace('万', '0000'))

参数说明
- name_in :定义 name 字段的输入处理器,先移除 HTML 标签,再去除前后空格。
- price_in :定义 price 字段的输入处理器,去除空格后将“万”替换为“0000”,实现价格标准化。
- default_output_processor :设置默认输出处理器为 TakeFirst ,即取第一个非空值。

这种设计使得数据清洗逻辑可以模块化、复用性强,极大提升了代码的可读性与可维护性。

3.2 XPath与CSS选择器的实践应用

XPath 和 CSS 选择器是网页数据提取的两大核心工具。XPath 更适合复杂结构的节点提取,CSS 选择器则在类名、属性匹配上更具优势。

3.2.1 提取房屋名称、价格、面积等关键字段

以房天下网站为例,假设我们要提取如下字段:

字段名称 提取方式 示例
房屋名称 XPath //h1[@class="house-title"]/text()
价格 CSS .price span::text
面积 XPath //span[@class="area"]/text()
户型 CSS .house-type::text

我们可以将这些提取逻辑统一放入 ItemLoader

loader = ItemLoader(item=HouseInfoItem(), response=response)
loader.add_xpath('name', '//h1[@class="house-title"]/text()')
loader.add_css('price', '.price span::text')
loader.add_xpath('area', '//span[@class="area"]/text()')
loader.add_css('type', '.house-type::text')

逻辑分析
- 使用 add_xpath add_css 方法分别绑定字段与选择器。
- 字段名应与 HouseInfoItem 中定义的字段保持一致。
- 通过这种方式,字段提取与数据清洗可以完全解耦,便于后续维护。

3.2.2 多种选择器混合使用的技巧

在某些复杂页面中,可能需要结合 XPath 和 CSS 进行嵌套提取。例如:

loader.add_xpath('price', '//div[@class="price"]/span[@itemprop="price"]/text()')
loader.add_css('price', '.price span[itemprop="price"]::text')

分析
- 第一种写法使用 XPath 的嵌套路径,精确匹配 price 下的 span
- 第二种写法使用 CSS 属性选择器,同样可以实现目标。
- 两种方式在提取效果上等价,但在可读性和结构上略有差异。

此外,我们还可以在选择器中使用正则表达式来提取特定模式的内容:

loader.add_xpath('area', '//span[@class="area"]/text()', re='(\d+\.?\d*)㎡')

说明
- 使用 re 参数传入正则表达式,仅提取 xx㎡ 格式中的数字部分。
- 此方式可以有效过滤无关文本,提升字段准确性。

3.3 数据提取的优化策略

在实际爬虫项目中,数据提取过程常面临字段缺失、数据重复、格式混乱等问题。因此,必须采用一定的策略进行优化。

3.3.1 数据重复与缺失的应对方式

数据重复问题

数据重复通常出现在分页抓取或跳转抓取过程中。解决方法包括:

  • 使用指纹去重 :Scrapy 内置了 RFPDupeFilter ,可以基于请求指纹判断是否重复。
  • 自定义去重逻辑 :如基于 url house_id 建立缓存,避免重复采集。
数据缺失问题

字段缺失可能导致后续处理失败。我们可以使用如下方式处理:

  • 设置默认值
loader.add_xpath('price', '//div[@class="price"]/text()', default='0')
  • 使用默认处理器
class HouseInfoItem(scrapy.Item):
    name = scrapy.Field(default='未知')
    price = scrapy.Field(default=0)

分析
- 设置默认值可以防止字段为空,避免在后续 Pipeline 中抛出异常。
- 使用默认处理器时,需确保字段类型与默认值匹配,如 str 类型不能设置为 0

3.3.2 字段清洗与标准化处理

字段清洗是数据质量保障的关键步骤。以下是一些常见的清洗策略:

示例:价格字段标准化
def clean_price(value):
    if '万' in value:
        return float(value.replace('万', '')) * 10000
    elif '元/㎡' in value:
        return float(value.replace('元/㎡', '').strip())
    else:
        return float(value)

loader.add_css('price', '.price::text', MapCompose(clean_price))

说明
- 定义 clean_price() 函数统一处理不同格式的价格。
- 使用 MapCompose() 将清洗函数加入处理器链。

示例:面积字段处理
def clean_area(value):
    return value.replace('㎡', '').strip()

loader.add_xpath('area', '//span[@class="area"]/text()', MapCompose(clean_area))

分析
- 移除单位“㎡”,确保面积字段为纯数字。
- 清洗后字段可直接用于数值比较或数据库存储。

示例:字段格式统一

针对地区字段,可能出现“北京市 | 朝阳区”、“北京市朝阳区”等不同格式,可统一为“北京市-朝阳区”:

def unify_location(value):
    return value.replace('|', '-').replace(' ', '-')

loader.add_css('location', '.location::text', MapCompose(unify_location))

分析
- 统一字段格式,方便后续分析或数据库查询。
- 使用 MapCompose 可链式调用多个清洗函数。

补充:Mermaid 流程图说明数据提取流程

graph TD
    A[开始解析页面] --> B{是否使用ItemLoader?}
    B -->|是| C[创建ItemLoader实例]
    C --> D[绑定字段与XPath/CSS选择器]
    D --> E[应用Input Processor清洗数据]
    E --> F[应用Output Processor输出字段值]
    F --> G[构建Item对象]
    G --> H[返回Item供Pipeline处理]
    B -->|否| I[手动提取字段并构造字典]
    I --> J[处理数据缺失与格式问题]
    J --> H

流程说明
- 使用 ItemLoader 可以将字段提取与清洗逻辑模块化,提高代码可维护性。
- 未使用 ItemLoader 时,需要手动处理字段缺失、格式不统一等问题,逻辑分散,维护困难。

小结

本章详细讲解了如何利用 ItemLoader 提高字段提取的可维护性,结合 XPath 与 CSS 选择器进行高效数据提取,并通过处理器链实现字段清洗与标准化。同时,针对数据重复、缺失等问题,提出了实用的应对策略。这些内容为后续章节中数据的持久化与存储奠定了坚实基础。

下一章我们将介绍如何定义 HouseInfoItem 数据结构,以便在后续处理中统一数据格式。

4. HouseInfoItem数据结构定义

在构建Scrapy爬虫项目的过程中,数据结构的设计是至关重要的一步。Scrapy通过 Item 类来定义爬取数据的结构,类似于数据库中的表结构。良好的数据结构设计不仅有助于数据的提取与清洗,还能为后续的数据存储、分析和展示提供清晰的接口。本章将围绕 HouseInfoItem 这一具体的数据结构展开讲解,包括其定义方式、字段设计原则以及在Spider和Pipeline中的使用方式。

4.1 定义Item类

4.1.1 Scrapy中Item类的声明方式

Scrapy的 Item 类本质上是一个Python类,继承自 scrapy.Item ,并通过 scrapy.Field() 定义字段。每个字段代表一个数据属性,例如房屋名称、价格、面积等。

下面是一个基本的 HouseInfoItem 类定义示例:

import scrapy

class HouseInfoItem(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    area = scrapy.Field()
    location = scrapy.Field()
    publish_date = scrapy.Field()

代码逐行解读:

  • 第1行:导入Scrapy模块。
  • 第3行:定义 HouseInfoItem 类,继承自 scrapy.Item
  • 第4-8行:使用 scrapy.Field() 定义字段,分别表示房屋名称、价格、面积、位置和发布时间。

字段说明:

  • name :房屋名称,通常为字符串类型。
  • price :房屋价格,可能是字符串或浮点数,取决于数据源格式。
  • area :房屋面积,单位通常为平方米,类型为字符串或浮点数。
  • location :房屋所在位置,通常由区域和具体地址组成。
  • publish_date :房源发布时间,类型为字符串或日期时间对象。

这种声明方式使得每个字段都有明确的语义,便于后续处理和映射到数据库表结构。

4.1.2 常见字段类型与默认值设置

虽然Scrapy的 Field() 本身不强制字段类型,但可以在字段定义时通过元数据(metadata)或后续处理(如ItemLoader的Processor)指定数据类型和默认值。

例如,我们可以为 price 字段设置默认值为 0 ,并在元数据中标注其类型为浮点型:

class HouseInfoItem(scrapy.Item):
    price = scrapy.Field(
        metadata={'type': 'float'},
        default=0.0
    )
    area = scrapy.Field(
        metadata={'type': 'float'},
        default=0.0
    )
    location = scrapy.Field(default='未知')

字段说明:

  • metadata :用于附加字段元信息,例如类型、单位等。
  • default :字段的默认值,当页面中未提取到该字段时使用。

通过这种方式,可以增强数据结构的健壮性和可维护性,尤其在数据清洗和存储阶段非常有用。

4.2 数据字段的逻辑梳理

4.2.1 房屋信息字段的完整性设计

为了确保抓取数据的完整性, HouseInfoItem 的字段设计应涵盖房源信息的核心要素。以下是一个较为完整的房屋信息字段清单:

字段名 数据类型 描述
name string 房屋名称或标题
price float 房屋价格(元/平米或总价)
area float 建筑面积
unit_price float 单价(元/平米)
location string 房屋所在位置
community string 所属小区名称
room_type string 户型(几室几厅)
floor string 楼层信息
orientation string 房屋朝向
publish_date string 发布时间
source_url string 数据来源页面的URL

字段设计逻辑分析:

  • price unit_price 区分:有些页面显示总价,有些显示单价,应分别保留。
  • room_type floor :属于房源的基本属性,便于用户筛选。
  • source_url :用于数据溯源,便于后续调试或去重处理。

通过字段完整性设计,可以确保抓取数据在业务层面具备完整的描述能力。

4.2.2 数据类型与字段命名规范

字段命名应遵循清晰、简洁、一致的原则,通常使用小写字母和下划线分隔,如 publish_date unit_price 。数据类型则应根据实际内容合理选择:

  • 字符串类型(string) :适用于描述性字段,如名称、地址、时间等。
  • 数值类型(float / int) :适用于价格、面积、楼层等可参与计算的字段。
  • 布尔类型(boolean) :适用于状态类字段,如是否已售、是否置顶等。

此外,命名应避免歧义,例如:

  • area :可能指建筑面积或使用面积,建议使用 building_area usable_area
  • unit_price :明确表示单价,避免混淆。

统一的命名规范不仅能提升代码可读性,也便于后续与其他系统(如数据库、前端)对接。

4.3 Item对象的使用与传递

4.3.1 在Spider中构建Item对象

在Spider中,我们通常通过解析响应(response)提取数据,并将结果填充到 Item 对象中。

以下是一个典型的Spider中构建 HouseInfoItem 的示例:

import scrapy
from tutorial.items import HouseInfoItem

class FangSpider(scrapy.Spider):
    name = 'fang'
    start_urls = ['https://www.fang.com/SoufunFamily.htm']

    def parse(self, response):
        for house in response.css('.house-list li'):
            item = HouseInfoItem()
            item['name'] = house.css('.title a::text').get()
            item['price'] = house.css('.price::text').get()
            item['area'] = house.css('.area::text').get()
            item['location'] = house.css('.location::text').get()
            item['publish_date'] = house.css('.time::text').get()
            yield item

代码逻辑分析:

  • 第1-2行:导入Scrapy模块与自定义Item类。
  • 第4-7行:定义Spider类及起始URL。
  • 第9行:定义 parse 方法,用于处理响应。
  • 第11行:使用CSS选择器遍历房源列表。
  • 第12行:初始化一个 HouseInfoItem 实例。
  • 第13-17行:通过CSS选择器提取字段值,并赋值给Item对象。
  • 第18行:使用 yield 将Item对象传递给后续Pipeline处理。

这种方式使得Spider专注于数据提取,而数据处理逻辑则交由Pipeline完成,实现职责分离。

4.3.2 Pipeline中对Item的处理流程

Scrapy的Pipeline负责处理Spider提取的Item对象,通常包括数据清洗、验证、去重、持久化等操作。

下面是一个简单的Pipeline示例,用于清洗价格字段并存储到日志中:

class PriceCleanPipeline:
    def process_item(self, item, spider):
        if item['price']:
            # 去除价格中的非数字字符
            cleaned_price = ''.join(filter(str.isdigit, item['price']))
            item['price'] = float(cleaned_price) if cleaned_price else 0.0
        return item

代码逻辑分析:

  • process_item :每个Item都会经过该方法处理。
  • 使用 filter(str.isdigit, item['price']) 过滤非数字字符。
  • 将清洗后的字符串转换为浮点数,若为空则设为0.0。

另一个Pipeline示例用于将Item写入日志文件:

import logging

class LoggingPipeline:
    def open_spider(self, spider):
        logging.info("Spider started: %s", spider.name)

    def process_item(self, item, spider):
        logging.debug("Processed item: %s", dict(item))
        return item

    def close_spider(self, spider):
        logging.info("Spider finished: %s", spider.name)

代码逻辑分析:

  • open_spider :Spider启动时记录日志。
  • process_item :记录每个Item的内容。
  • close_spider :Spider结束时记录日志。

Pipeline调用流程图:

graph TD
    A[Spider yield Item] --> B[PriceCleanPipeline]
    B --> C[LoggingPipeline]
    C --> D[其他Pipeline或存储]

流程说明:

  1. Spider提取数据并生成Item对象。
  2. Item进入第一个Pipeline(如 PriceCleanPipeline )进行数据清洗。
  3. 清洗后的Item继续传递到后续Pipeline(如 LoggingPipeline )。
  4. 最终,Item可能被写入数据库、文件或进行其他处理。

通过Pipeline链式处理,可以实现模块化、可扩展的数据处理流程,提升代码的可维护性与灵活性。

总结

在本章中,我们详细讲解了Scrapy中 HouseInfoItem 的定义与使用方式,包括:

  • 如何声明Item类并设置字段元数据与默认值;
  • 如何设计完整的房屋信息字段结构,确保数据完整性;
  • 如何在Spider中构建Item对象并将其传递给Pipeline;
  • 如何在Pipeline中对Item进行清洗、记录等处理操作;
  • 使用Mermaid流程图展示了Item对象在Spider与Pipeline之间的流转路径。

下一章将深入讲解如何使用 FormRequest 模拟登录房天下网站,突破需要登录才能访问的页面限制。

5. 使用FormRequest模拟登录房天下网站

在进行网络爬虫开发过程中,部分网站为了保护用户数据安全,设置了登录权限,只有登录后才能访问特定页面。Scrapy 提供了 FormRequest 类,能够帮助我们模拟表单提交,完成登录操作。本章将深入探讨如何通过 Scrapy 的 FormRequest 实现模拟登录房天下网站,并处理登录后的页面访问与异常处理机制。

5.1 登录机制分析

在模拟登录之前,首先需要对房天下网站的登录机制进行深入分析,包括请求方式、参数结构以及 Cookie 和 Session 的管理方式。

5.1.1 表单提交与请求方式识别

房天下网站的登录通常通过 HTML 表单实现,用户在输入用户名和密码后点击登录按钮,浏览器会将这些信息通过 POST 请求发送到服务器。

使用浏览器的开发者工具(F12),我们可以在“Network”选项卡中观察登录请求的详细信息:

  • 请求地址(URL) :通常是 /login 或类似的路径。
  • 请求方法(Method) :POST。
  • 请求头(Headers) :包含 Content-Type: application/x-www-form-urlencoded
  • 请求体(Body) :包含用户名(username)、密码(password)等字段。

例如,一个典型的登录请求体可能如下所示:

username=testuser&password=123456

5.1.2 Cookie与Session的获取与管理

登录成功后,服务器通常会返回一个 Session ID,并通过 Set-Cookie 响应头将其保存在浏览器中。后续请求时,浏览器会自动携带该 Cookie,服务器通过 Cookie 识别用户身份。

在 Scrapy 中,Spider 会自动管理 Cookie,即当使用 FormRequest 登录后,后续的请求会自动带上服务器返回的 Cookie,实现登录状态的维持。

5.2 使用FormRequest实现登录

Scrapy 提供了 FormRequest 类,可以方便地模拟 POST 表单提交,实现模拟登录。以下将详细介绍如何构建 FormRequest 请求并传递参数。

5.2.1 构建FormRequest请求

FormRequest 的基本使用方式如下:

import scrapy

class FangSpider(scrapy.Spider):
    name = 'fang_login'
    login_url = 'https://login.fang.com/login'

    def start_requests(self):
        yield scrapy.FormRequest(
            url=self.login_url,
            formdata={'username': 'your_username', 'password': 'your_password'},
            callback=self.after_login
        )

    def after_login(self, response):
        # 登录成功后的处理逻辑
        self.log("登录成功")
        # 可以继续发起其他请求,如访问个人中心页面
        yield scrapy.Request(url='https://user.fang.com/dashboard', callback=self.parse_dashboard)

    def parse_dashboard(self, response):
        # 解析登录后的页面内容
        self.log("访问个人中心页面成功")
        # 提取所需数据

代码说明
- scrapy.FormRequest 是用于模拟表单提交的类。
- formdata 参数用于设置提交的表单字段,如用户名和密码。
- callback 指定登录成功后调用的回调函数。

5.2.2 模拟POST请求与参数传递

在某些情况下,网站可能使用 JavaScript 动态生成表单参数,或者需要额外的 token、加密字段等。此时,我们可能需要手动构造请求体。

例如,若登录请求中包含加密字段 token ,可以通过如下方式传递:

yield scrapy.FormRequest(
    url='https://login.fang.com/login',
    formdata={
        'username': 'your_username',
        'password': 'your_password',
        'token': 'encrypted_token'
    },
    callback=self.after_login
)

此外,还可以使用 scrapy.Request 手动构造 POST 请求:

yield scrapy.Request(
    url='https://login.fang.com/login',
    method='POST',
    headers={'Content-Type': 'application/x-www-form-urlencoded'},
    body='username=your_username&password=your_password&token=encrypted_token',
    callback=self.after_login
)

这种方式更灵活,适用于复杂表单结构。

5.3 登录后的页面访问与权限验证

登录成功后,我们需要验证是否能够正常访问受保护的页面,并确保登录状态的有效性。

5.3.1 登录成功后的页面抓取

after_login 回调函数中,我们可以继续发起新的请求,访问登录后才能查看的页面。例如:

def after_login(self, response):
    # 检查是否登录成功
    if "欢迎" in response.text:
        self.log("登录成功")
        yield scrapy.Request(
            url='https://user.fang.com/myhouse',
            callback=self.parse_myhouse
        )
    else:
        self.log("登录失败")

在这个例子中,我们通过判断响应内容中是否包含“欢迎”关键词,来判断登录是否成功。

5.3.2 登录状态维持与异常处理

Scrapy 会自动维持 Cookie,因此在登录后发起的请求都会携带登录时获取的 Cookie。但为了提高程序的健壮性,建议添加异常处理机制:

def parse_myhouse(self, response):
    if response.status == 200:
        self.log("访问个人房源页面成功")
        # 提取数据
    else:
        self.log(f"访问失败,状态码:{response.status}")
        # 可以尝试重新登录
异常处理策略
  1. 登录失败重试机制 :可设置重试次数,或记录失败日志。
  2. 验证码识别 :部分网站登录时会要求输入验证码,需结合 OCR 或第三方识别服务。
  3. Session 过期处理 :长时间未操作可能导致 Session 过期,可通过中间件检测并重新登录。
  4. 请求失败重试 :使用 retry 中间件,自动重试失败请求。

5.3.3 模拟登录流程图

以下是使用 FormRequest 模拟登录房天下的流程图:

graph TD
    A[开始爬取] --> B[发送FormRequest登录请求]
    B --> C{登录是否成功?}
    C -->|是| D[进入个人中心页面]
    C -->|否| E[记录失败日志/重新尝试登录]
    D --> F[提取登录后页面数据]
    E --> G[结束爬取]
    F --> G

5.3.4 模拟登录请求参数对照表

以下是一个简单的登录请求参数对照表,用于理解表单字段的含义:

参数名 示例值 说明
username testuser 用户名或手机号
password * * 加密后的密码
token abc123xyz 登录令牌,用于防重放攻击
remember_me on 是否记住登录状态

5.3.5 登录后的数据抓取代码示例

以下是一个完整的登录后抓取个人房源信息的代码示例:

import scrapy

class FangLoginSpider(scrapy.Spider):
    name = 'fang_login_spider'
    login_url = 'https://login.fang.com/login'
    dashboard_url = 'https://user.fang.com/myhouse'

    def start_requests(self):
        yield scrapy.FormRequest(
            url=self.login_url,
            formdata={'username': 'your_username', 'password': 'your_password'},
            callback=self.after_login
        )

    def after_login(self, response):
        if "欢迎" in response.text:
            self.log("登录成功,准备访问房源页面")
            yield scrapy.Request(url=self.dashboard_url, callback=self.parse_house_info)
        else:
            self.log("登录失败,响应内容:", response.text)

    def parse_house_info(self, response):
        houses = response.css('.house-list li')
        for house in houses:
            yield {
                'title': house.css('.title::text').get(),
                'price': house.css('.price::text').get(),
                'area': house.css('.area::text').get(),
                'link': house.css('a::attr(href)').get()
            }

代码说明
- 使用 FormRequest 发起登录请求。
- 登录成功后访问个人房源页面。
- 使用 CSS 选择器提取房源标题、价格、面积和链接。
- 每个房源信息作为字典 yield 出去,供 Pipeline 处理。

5.3.6 登录逻辑优化建议

在实际项目中,为了增强爬虫的稳定性,建议对登录逻辑进行如下优化:

  1. 动态密码加密 :部分网站对密码进行加密传输,需在 Scrapy 中模拟加密算法。
  2. 验证码识别 :引入第三方 OCR 服务或手动输入验证码。
  3. User-Agent 随机化 :防止因固定 UA 被封禁。
  4. 设置下载延迟 :在 settings.py 中配置 DOWNLOAD_DELAY ,防止频繁请求被封 IP。
  5. 使用代理 IP :构建代理池,轮换 IP,避免被网站封禁。

通过本章的学习,我们掌握了使用 Scrapy 的 FormRequest 实现模拟登录房天下网站的核心流程,包括登录机制分析、请求构造、参数传递、登录后页面访问与异常处理等内容。这些知识为后续章节中访问受保护资源、爬取登录后数据提供了坚实基础。

6. Scrapy爬虫数据持久化到MySQL数据库

在数据爬取完成后,如何将抓取到的信息高效、安全地存储到数据库中,是整个爬虫项目中至关重要的一个环节。Scrapy 提供了灵活的 Pipeline 机制,使得我们可以将爬取的数据写入到各种持久化存储系统中,如文件、MongoDB、MySQL 等。本章将重点讲解如何将爬取的房屋信息数据通过 Scrapy 的 Pipeline 机制,持久化到 MySQL 数据库中。

本章内容将从数据库设计、Scrapy 与 MySQL 的连接方式,到数据持久化实现的完整流程进行深入解析,并结合代码示例展示如何构建一个高效、稳定的数据写入流程。

6.1 数据库设计与建模

在将数据写入 MySQL 之前,首先需要完成数据库和数据表的设计,确保字段结构与爬虫抓取的数据字段一致,并具备良好的扩展性与查询性能。

6.1.1 MySQL数据库的创建与表结构设计

我们首先创建一个名为 house_info 的数据库,用于存储房天下网站抓取的房屋信息数据。接着设计一个名为 house_data 的数据表,用于存储房屋的关键信息。

创建数据库与数据表的 SQL 语句如下:
-- 创建数据库
CREATE DATABASE house_info;

-- 使用数据库
USE house_info;

-- 创建房屋信息表
CREATE TABLE house_data (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL COMMENT '房屋名称',
    price DECIMAL(10, 2) NOT NULL COMMENT '房屋价格(万元)',
    area DECIMAL(10, 2) NOT NULL COMMENT '建筑面积(平方米)',
    location VARCHAR(255) COMMENT '房屋位置',
    house_type VARCHAR(100) COMMENT '户型(如:三室两厅)',
    publish_date DATE COMMENT '发布时间',
    url VARCHAR(512) NOT NULL COMMENT '房屋详情页链接',
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
字段说明表:
字段名 类型 是否为空 描述
id INT 自增主键
name VARCHAR(255) 房屋名称
price DECIMAL(10,2) 房屋价格(单位:万元)
area DECIMAL(10,2) 建筑面积(单位:平方米)
location VARCHAR(255) 房屋位置
house_type VARCHAR(100) 户型信息
publish_date DATE 发布时间
url VARCHAR(512) 房屋详情页链接
create_time TIMESTAMP 记录创建时间

6.1.2 房屋信息字段与数据库字段的映射关系

接下来,我们需要将 Scrapy 爬虫抓取到的字段与数据库表字段进行映射。以我们定义的 HouseInfoItem 为例,其字段与数据库字段的对应关系如下:

class HouseInfoItem(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    area = scrapy.Field()
    location = scrapy.Field()
    house_type = scrapy.Field()
    publish_date = scrapy.Field()
    url = scrapy.Field()

映射关系如下:

Scrapy Item字段 MySQL字段名
name name
price price
area area
location location
house_type house_type
publish_date publish_date
url url

这样我们就完成了数据库结构的设计与字段映射,接下来进入 Scrapy 与 MySQL 的连接配置。

6.2 Scrapy与MySQL的连接方式

Scrapy 本身并不直接提供与 MySQL 的连接功能,但可以通过 Python 的第三方库(如 pymysql mysqlclient )来实现数据库操作。本节将介绍使用 pymysql 库实现数据库连接的方法。

6.2.1 使用pymysql库进行数据库连接

首先,确保已安装 pymysql

pip install pymysql

接下来,我们可以在 Scrapy 的 settings.py 中配置数据库连接参数,以便在 Pipeline 中复用这些配置。

示例:在 settings.py 中添加如下配置:
MYSQL_CONFIG = {
    'host': 'localhost',
    'port': 3306,
    'user': 'root',
    'password': 'your_password',
    'database': 'house_info',
    'charset': 'utf8mb4',
}
示例:使用 pymysql 连接数据库的代码如下:
import pymysql

def connect_to_mysql():
    conn = pymysql.connect(
        host='localhost',
        port=3306,
        user='root',
        password='your_password',
        database='house_info',
        charset='utf8mb4',
        cursorclass=pymysql.cursors.DictCursor
    )
    return conn
代码解析:
  • host :数据库主机地址,默认为本地 localhost
  • port :MySQL 服务端口,默认为 3306
  • user/password :数据库登录用户名和密码。
  • database :连接的数据库名。
  • charset :设置字符集为 utf8mb4 ,支持中文和表情符号。
  • cursorclass :指定返回字典格式的结果,便于后续处理。

6.2.2 数据插入语句的编写与优化

为了将数据写入 MySQL,我们需要构建插入语句。建议使用参数化 SQL 插入,以防止 SQL 注入攻击。

示例插入语句:
INSERT INTO house_data (name, price, area, location, house_type, publish_date, url)
VALUES (%s, %s, %s, %s, %s, %s, %s)
参数化插入示例代码:
def insert_house_data(cursor, item):
    sql = """
    INSERT INTO house_data (name, price, area, location, house_type, publish_date, url)
    VALUES (%s, %s, %s, %s, %s, %s, %s)
    """
    values = (
        item['name'],
        item['price'],
        item['area'],
        item['location'],
        item['house_type'],
        item['publish_date'],
        item['url']
    )
    cursor.execute(sql, values)
优化建议:
  • 批量插入 :使用 executemany 方法进行批量插入,提升性能。
  • 事务控制 :使用 BEGIN / COMMIT / ROLLBACK 来确保数据一致性。
  • 连接池管理 :可结合 DBUtils SQLAlchemy 管理连接池,提高并发性能。

6.3 数据持久化的实现

Scrapy 提供了 Item Pipeline 机制,允许我们对抓取的数据进行清洗、验证和持久化操作。我们将通过自定义一个 MySQL Pipeline,将房屋信息写入数据库。

6.3.1 编写Pipeline实现数据写入

示例:创建 mysql_pipeline.py 文件
import pymysql
from scrapy.utils.project import get_project_settings

class MySQLPipeline:
    def __init__(self):
        settings = get_project_settings()
        self.mysql_config = settings.get('MYSQL_CONFIG')
        self.conn = None
        self.cursor = None

    def open_spider(self, spider):
        """在爬虫启动时建立数据库连接"""
        self.conn = pymysql.connect(**self.mysql_config)
        self.cursor = self.conn.cursor()

    def close_spider(self, spider):
        """在爬虫结束时关闭数据库连接"""
        if self.cursor:
            self.cursor.close()
        if self.conn:
            self.conn.close()

    def process_item(self, item, spider):
        try:
            self._insert_item(item)
            self.conn.commit()
        except Exception as e:
            spider.logger.error(f"插入数据失败:{e}")
            self.conn.rollback()
        return item

    def _insert_item(self, item):
        sql = """
        INSERT INTO house_data (name, price, area, location, house_type, publish_date, url)
        VALUES (%s, %s, %s, %s, %s, %s, %s)
        """
        values = (
            item['name'],
            item['price'],
            item['area'],
            item['location'],
            item['house_type'],
            item['publish_date'],
            item['url']
        )
        self.cursor.execute(sql, values)
说明:
  • open_spider() :在爬虫启动时建立数据库连接。
  • close_spider() :在爬虫结束时关闭连接,释放资源。
  • process_item() :核心方法,处理每个 Item 数据,执行插入并提交事务。
  • try-except :异常捕获机制,插入失败时回滚事务,防止脏数据。
settings.py 中启用 Pipeline:
ITEM_PIPELINES = {
    'house_info.pipelines.MySQLPipeline': 300,
}

6.3.2 异常处理与事务控制

在数据写入过程中,可能会遇到字段缺失、数据库连接中断、主键冲突等问题。因此,合理的异常处理和事务控制机制至关重要。

常见异常处理策略:
  • 字段校验 :在 Pipeline 中加入字段非空校验。
  • 重试机制 :遇到数据库连接失败时可加入重试逻辑。
  • 日志记录 :使用 spider.logger 记录错误信息,便于排查问题。
事务控制示例:
def process_item(self, item, spider):
    try:
        self._insert_item(item)
        self.conn.commit()
    except pymysql.MySQLError as e:
        spider.logger.error(f"MySQL 错误:{e}")
        self.conn.rollback()
    except Exception as e:
        spider.logger.error(f"未知错误:{e}")
        self.conn.rollback()
    return item
流程图说明:
graph TD
    A[开始处理Item] --> B{数据库连接正常?}
    B -->|是| C[执行插入操作]
    B -->|否| D[记录错误日志]
    C --> E{插入成功?}
    E -->|是| F[提交事务]
    E -->|否| G[回滚事务]
    F --> H[返回Item]
    G --> I[记录错误日志]
    D --> I
    I --> J[返回Item]

通过本章的学习,我们了解了如何设计 MySQL 数据库表结构,配置 Scrapy 与 MySQL 的连接方式,并通过自定义 Pipeline 实现数据的持久化写入。下一章将围绕反爬策略展开,介绍如何在 Scrapy 中实现反爬对抗与请求优化。

7. 反爬策略应对方法与爬虫道德规范

在实际的网络爬虫开发过程中,尤其是面向如房天下这类具有较强反爬机制的网站时,爬虫开发者需要具备识别和应对常见反爬策略的能力,同时也必须遵守基本的网络爬虫道德规范,以避免被目标网站封禁IP、限制访问,甚至引发法律风险。

7.1 反爬策略分析与应对

7.1.1 请求频率限制与下载延迟设置

许多网站通过限制单位时间内来自同一IP的请求次数来防止爬虫行为。Scrapy 提供了 DOWNLOAD_DELAY 设置项,用于控制请求之间的最小间隔时间。

设置方式:

settings.py 中添加如下配置:

# 设置下载延迟为2秒
DOWNLOAD_DELAY = 2

# 随机延迟,避免固定间隔被识别
RANDOMIZE_DOWNLOAD_DELAY = True

参数说明:
- DOWNLOAD_DELAY : 每个请求之间的最小延迟(秒)
- RANDOMIZE_DOWNLOAD_DELAY : 如果为 True,则实际延迟为 0.5 * DOWNLOAD_DELAY 到 1.5 * DOWNLOAD_DELAY 之间的随机值

此外,还可以使用 AutoThrottle 插件动态调整下载延迟:

AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 1
AUTOTHROTTLE_MAX_DELAY = 60
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0

说明:该机制会根据服务器响应自动调整请求频率,以模拟更自然的用户行为。

7.1.2 IP代理池的构建与使用

当单一IP频繁访问目标网站时,极易被封禁。构建一个稳定的代理IP池是解决该问题的有效手段。

实现方式:

可以使用 scrapy-proxies 插件或自定义中间件来实现 IP 代理池功能。

示例:自定义代理中间件(proxies.py)

import random

class ProxyMiddleware:
    def __init__(self):
        self.proxies = [
            'http://192.168.1.10:8080',
            'http://192.168.1.11:8080',
            'http://192.168.1.12:8080'
        ]

    def process_request(self, request, spider):
        proxy = random.choice(self.proxies)
        request.meta['proxy'] = proxy

配置中间件:

settings.py 中启用中间件:

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.ProxyMiddleware': 543,
}

提示:代理IP建议使用付费代理服务或定期更新的免费代理库,以保证可用性。

7.2 请求头与User-Agent管理

7.2.1 设置随机User-Agent

User-Agent 是服务器识别爬虫的重要依据之一。为了避免被识别为爬虫,我们可以通过随机更换 User-Agent 模拟浏览器访问行为。

实现方式:

可以使用 fake-useragent 库实现随机 User-Agent。

安装:

pip install fake-useragent

示例:自定义 User-Agent 中间件

from fake_useragent import UserAgent

class RandomUserAgentMiddleware:
    def __init__(self):
        self.ua = UserAgent()

    def process_request(self, request, spider):
        request.headers.setdefault('User-Agent', self.ua.random)

配置中间件:

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.RandomUserAgentMiddleware': 542,
}

说明:每次请求时,User-Agent 都会随机选择一个主流浏览器的标识,有效降低被识别为爬虫的风险。

7.2.2 请求头模拟浏览器行为

除了 User-Agent,还需要模拟浏览器的其他请求头,如 Accept、Accept-Language、Referer 等。

示例:完整请求头设置

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0 Safari/537.36',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Referer': 'https://www.baidu.com/',
    'Connection': 'keep-alive'
}

在 Spider 中使用:

yield scrapy.Request(url, headers=headers, callback=self.parse)

7.3 遵守robots.txt规则与爬虫道德规范

7.3.1 robots.txt文件的解析与遵守

每个网站都会提供一个 robots.txt 文件,用于声明哪些页面允许爬取,哪些禁止爬取。

示例:房天下网站 robots.txt 文件片段

User-agent: *
Disallow: /house/
Disallow: /member/

说明:上述规则表示所有 User-Agent(爬虫)都不允许访问 /house/ /member/ 路径下的页面。

Scrapy 遵守 robots.txt 的配置:

# settings.py
ROBOTSTXT_OBEY = True

说明:启用该配置后,Scrapy 会自动解析目标网站的 robots.txt 文件,并跳过被禁止的路径。

7.3.2 合理设置爬取范围与频率,避免对服务器造成压力

爬虫开发者应具备良好的网络道德意识,合理设置爬取策略,避免对目标网站造成过大负载。

建议措施:

  1. 设置合理的下载延迟 :如前文所述,使用 DOWNLOAD_DELAY AUTOTHROTTLE
  2. 限制并发请求数量
    python CONCURRENT_REQUESTS = 4
  3. 限制最大深度
    python DEPTH_LIMIT = 2
  4. 避免重复请求
    python DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'

此外,开发者应避免在高峰期抓取数据,尽量选择服务器负载较低的时间段进行爬取。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本文介绍如何使用Python的Scrapy框架爬取房天下网站的房产信息,并将抓取的数据存储到MySQL数据库中。Scrapy是一个功能强大的网络爬虫框架,MySQL则用于结构化数据存储。通过本项目实战,读者可掌握爬虫项目搭建、网页解析、模拟登录、数据存储等关键技术,适用于数据分析、数据采集等场景。项目还涉及反爬应对、数据清洗和异常处理等内容,具备完整的爬虫开发流程。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐