Python Scrapy 跨平台爬虫实战:XPath 解析与结构化数据提取

简介: Python Scrapy 跨平台爬虫实战:XPath 解析与结构化数据提取

爬虫开发中,请求—下载—解析—存储是最基础的四段流水线。请求和下载部分各语言方案大同小异,真正拉开效率差距的是解析层。BeautifulSoup 面对深层嵌套、条件筛选时力不从心;正则可读性差、维护成本高。XPath 是 W3C 标准查询语言,专为树结构设计,配合 Scrapy 的异步引擎,在大规模、跨平台爬虫项目中几乎没有对手。
一、Scrapy 项目初始化
pip install scrapy
scrapy startproject multispider && cd multispider
scrapy genspider technews example.com
在 items.py 中声明结构化字段:
import scrapy

class NewsItem(scrapy.Item):
title = scrapy.Field()
url = scrapy.Field()
author = scrapy.Field()
publish_date = scrapy.Field()
content = scrapy.Field()
tags = scrapy.Field()
source = scrapy.Field()
二、XPath 高频语法速查
场景 表达式 说明
全局搜索 //div[@class="list"] 不关心层级
相对定位 .//h2/a/@href 以当前节点为根,实战最关键
模糊匹配 contains(@class, "active") 多 class 场景必用
位置限定 //li[position()<=3] 取前 N 个
轴遍历 //h2/following-sibling::p 取兄弟节点
条件排除 //p[not(contains(@class,"ad"))] XPath 原生过滤广告
核心原则:循环遍历列表项时,子元素 XPath 必须以 . 开头(.//),否则会回到整个文档根节点全局搜索,导致数据错位。
三、核心爬虫:列表页 → 详情页两级解析
编辑 spiders/technews.py:
import scrapy
from multispider.items import NewsItem

class TechNewsSpider(scrapy.Spider):
name = 'technews'
allowed_domains = ['example.com']
start_urls = ['https://example.com/news']

def parse(self, response):
    # 列表页:定位所有文章条目
    for article in response.xpath('//div[@class="article-list"]/article'):
        detail_url = article.xpath('.//h2/a/@href').get()
        if detail_url:
            yield response.follow(
                detail_url,
                callback=self.parse_detail,
                meta={'list_title': article.xpath('.//h2/a/text()').get(default='').strip()}
            )

    # 翻页
    next_page = response.xpath('//a[contains(@class,"next")]/@href').get()
    if next_page:
        yield response.follow(next_page, callback=self.parse)

def parse_detail(self, response):
    item = NewsItem()
    item['title'] = (
        response.xpath('//h1[@class="article-title"]/text()').get(default='').strip()
        or response.meta.get('list_title', '')
    )
    item['url']          = response.url
    item['author']       = response.xpath('//span[@class="author-name"]/text()').get(default='匿名').strip()
    item['publish_date'] = response.xpath('//time[@class="publish-date"]/@datetime').get()
    item['tags']         = response.xpath('//div[@class="tags"]//a/text()').getall()
    item['source']       = 'technews'

    # 正文提取:排除广告/推荐节点
    paragraphs = response.xpath(
        '//div[@class="article-body"]'
        '//p[not(contains(@class,"ad")) and not(contains(@class,"recommend"))]'
        '/text()'
    ).getall()
    item['content'] = '\n'.join(p.strip() for p in paragraphs if p.strip())

    yield item

四个关键技巧:

  1. .// 相对路径:循环体内必须用 . 开头,避免跨条目误抓
  2. get(default=''):防止 NoneType 错误,提供安全兜底
  3. response.follow():自动补全相对 URL,无需手动拼域名
  4. meta 透传:列表页元数据传递到详情页,做 fallback 容错
    四、跨平台适配:规则配置与爬虫逻辑解耦
    不同站点 HTML 结构不同,但数据模型和清洗逻辑完全可复用。核心思路是将 XPath 规则抽成配置字典:
    SITE_RULES = {
    'siteA': {
     'start_urls':    ['https://site-a.com/news'],
     'list_item':     '//div[@class="news-item"]',
     'detail_link':   './/a[@class="title"]/@href',
     'title':         '//h1[@class="post-title"]/text()',
     'author':        '//span[@itemprop="author"]/text()',
     'publish_date':  '//meta[@property="article:published_time"]/@content',
     'content':       '//div[@class="post-content"]//p/text()',
     'tags':          '//div[@class="tag-list"]//a/text()',
     'next_page':     '//a[@rel="next"]/@href',
    
    },
    'siteB': {
     # ... 另一个站点的规则
    
    },
    }

class MultiSiteSpider(scrapy.Spider):
name = 'multisite'

def start_requests(self):
    for site_name, rules in SITE_RULES.items():
        for url in rules['start_urls']:
            yield scrapy.Request(url, callback=self.parse_list,
                                 meta={'site_name': site_name, 'rules': rules})

def parse_list(self, response):
    rules = response.meta['rules']
    for article in response.xpath(rules['list_item']):
        link = article.xpath(rules['detail_link']).get()
        if link:
            yield response.follow(link, callback=self.parse_detail, meta=response.meta)
    # 翻页
    next_page = response.xpath(rules['next_page']).get()
    if next_page:
        yield response.follow(next_page, callback=self.parse_list, meta=response.meta)

def parse_detail(self, response):
    rules = response.meta['rules']
    item = NewsItem()
    item['url']    = response.url
    item['source'] = response.meta['site_name']
    item['title']  = response.xpath(rules['title']).get(default='').strip()
    item['author'] = response.xpath(rules['author']).get(default='匿名').strip()
    item['content'] = '\n'.join(p.strip() for p in response.xpath(rules['content']).getall() if p.strip())
    item['tags']   = response.xpath(rules['tags']).getall()
    yield item

新增站点只需加一段规则配置,核心代码零改动——这是 Scrapy 跨平台扩展的工程优势。
五、接入代理 IP:突破反爬封锁
跨平台大规模爬虫必然触发目标站点的 IP 频率限制。以亿牛云爬虫代理为例,在 Scrapy 中接入代理只需编写一个下载器中间件。
新建 middlewares.py:
import base64
import random

def base64ify(bytes_or_str):
"""生成 Proxy-Authorization 认证头"""
input_bytes = bytes_or_str.encode('utf8') if isinstance(bytes_or_str, str) else bytes_or_str
return base64.urlsafe_b64encode(input_bytes).decode('ascii')

class ProxyMiddleware(object):
def process_request(self, request, spider):

    # 亿牛云爬虫代理参数(官网 www.16yun.cn)
    proxyHost = "t.16yun.cn"
    proxyPort = "31111"
    proxyUser = "username"    # 替换为你的用户名
    proxyPass = "password"    # 替换为你的密码

    # 设置代理地址
    request.meta['proxy'] = f"http://{proxyHost}:{proxyPort}"

    # 添加认证头(Scrapy 2.6.2+ 可省略,会自动设置)
    request.headers['Proxy-Authorization'] = 'Basic ' + base64ify(f"{proxyUser}:{proxyPass}")

    # 设置 Proxy-Tunnel:相同随机数 = 相同出口 IP(适合需要登录态保持的场景)
    tunnel = random.randint(1, 10000)
    request.headers['Proxy-Tunnel'] = str(tunnel)

    # 如需每个请求强制切换 IP,关闭连接复用
    request.headers['Connection'] = "Close"

在 settings.py 中启用中间件并配置重试策略:
DOWNLOADER_MIDDLEWARES = {
'multispider.middlewares.ProxyMiddleware': 100,
}

代理认证失败(407)时自动重试

RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 407, 408, 429]

并发与限速

CONCURRENT_REQUESTS = 16
DOWNLOAD_DELAY = 0.5
DOWNLOAD_TIMEOUT = 15
代理 IP 使用要点:
场景 配置方式 说明
每次请求换 IP Connection: Close

  • 随机 Tunnel 最常用,适合批量抓取
    保持同一 IP 固定 Proxy-Tunnel
    值 适合需要登录/Cookie 缓存的流程
    HTTPS 站点 使用库原生代理认证 避免手动 Proxy-Authorization
    被转发到目标站
    407 错误 检查域名/端口/用户名/密码 认证信息错误
    429 错误 降低并发或增加延迟 请求速率超出订单上限
    六、数据清洗管道
    编辑 pipelines.py,将清洗逻辑与爬虫逻辑分离:
    import re
    import json
    from datetime import datetime
    from itemadapter import ItemAdapter
    from scrapy.exceptions import DropItem

class DataCleaningPipeline:
def process_item(self, item, spider):
adapter = ItemAdapter(item)

    # 去除控制字符和首尾空白
    for field in ['title', 'author', 'content']:
        val = adapter.get(field, '')
        if val:
            val = re.sub(r'[\x00-\x1f\x7f-\x9f\u00a0]', '', val).strip()
            adapter[field] = val if val else None

    # 标签去重
    tags = adapter.get('tags', [])
    seen, cleaned = set(), []
    for tag in (t.strip() for t in tags if t.strip()):
        key = tag.lower()
        if key not in seen:
            seen.add(key)
            cleaned.append(tag)
    adapter['tags'] = cleaned[:10]

    # 必填校验
    if not adapter.get('title'):
        raise DropItem("Missing title")
    return item

class JsonExportPipeline:
def open_spider(self, spider):
self.file = open('output.jsonl', 'w', encoding='utf-8')
def process_item(self, item, spider):
self.file.write(json.dumps(dict(item), ensure_ascii=False) + '\n')
return item
def close_spider(self, spider):
self.file.close()

settings.py

ITEM_PIPELINES = {
'multispider.pipelines.DataCleaningPipeline': 100,
'multispider.pipelines.JsonExportPipeline': 200,
}
七、调试与运行

Scrapy Shell 验证 XPath(写代码前必做)

scrapy shell 'https://example.com/news'

response.xpath('//h1[@class="article-title"]/text()').get()
'Python 3.12 新特性解析'

运行爬虫

scrapy crawl technews -o results.json
八、XPath 避坑指南
陷阱 错误写法 正确写法
全局搜索误抓 article.xpath('//h2/text()') article.xpath('.//h2/text()')
多 class 失配 @class="item active" contains(@class, "active")
空白未处理 .get()
直接用 .get(default='').strip()
编码乱码 默认编码 FEED_EXPORT_ENCODING='utf-8'
九、总结
Scrapy + XPath 的工程价值集中在三个层面:

  1. 解析层:XPath 的树结构查询能力远超 BeautifulSoup,深层嵌套、多条件筛选、跨轴遍历是原生优势
  2. 架构层:异步引擎 + 中间件 + Pipeline 天然支持大规模、跨平台扩展。规则配置与爬虫逻辑解耦,新增站点边际成本趋近于零
  3. 反爬层:通过代理 IP 中间件(如亿牛云爬虫代理)无缝接入 IP 池,Proxy-Tunnel 机制精确控制 IP 切换时机,配合 407 重试策略保障稳定性
    实际项目中,先用 scrapy shell 验证 XPath 表达式再写代码;清洗逻辑统一收敛到 Pipeline;代理中间件根据业务场景选择随机 IP 或固定 IP 模式。这三点做到位,爬虫的可维护性和稳定性会有质的提升。
相关文章
|
4天前
|
人工智能 定位技术 SEO
我学 GEO 第 15 天:终于知道AI GEO该如何做?
我是暴走的莉莉酱,边旅行边研究AI GEO的数字游民。专注普通人如何提升“AI可见度”——让AI在回答用户问题时准确识别、理解并推荐你。不讲玄学,只做可测、可调、可持续的GEO实践。
402 125
|
7天前
|
机器学习/深度学习 人工智能 调度
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
HappyHorse 1.1 是新一代视频生成大模型,全面升级动态表现力、角色一致性、指令遵循、视觉质感与音画协同能力。支持I2V/T2V/R2V三类生成,适配短剧、电商广告、品牌营销等场景,提供高质、流畅、可控的AI视频生产力。
683 4
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
|
4天前
|
缓存 人工智能 运维
阿里云618百炼大模型Qwen3.7-Max功能、免费试用、订阅计费、配置接入详解
Qwen3.7-MAX是阿里云百炼平台推出的通义千问3.7系列旗舰大语言模型,专为智能体时代复杂任务打造,依托阿里云全域算力与自研技术,在逻辑推理、长文本处理、代码工程、长周期自主执行等领域达到行业顶尖水平。2026年618期间,该模型推出多重免费试用权益、按量计费5折、订阅套餐优惠等专属福利,覆盖个人开发者、团队与企业全场景需求,以下从核心功能、免费试用、订阅计费、配置接入四方面展开详细解析。
394 123
|
3天前
|
人工智能 自然语言处理 API
阿里云Token Plan团队版解析:功能、三档套餐与省钱订阅指南
阿里云百炼平台推出的Token Plan团队版,是面向企业与团队的AI大模型订阅服务,以Credits为统一计量单位,整合文本与图像生成模型,提供团队管理、数据安全、多工具兼容等核心能力,解决团队零散订阅AI服务的管理混乱、成本失控、数据安全等痛点。本文将从核心定位、套餐详情、计费规则、团队管理、工具兼容、便宜订阅技巧等方面,全面解析Token Plan团队版,帮助企业与团队高效、低成本地使用AI服务。
297 108
|
18天前
|
缓存 测试技术 API
Qwen 3.7 Plus 与 Max 实测:性价比与多模态能力差异解析(2026)
2026 年 6 月 1 日,阿里悄无声息地发布了 Qwen 3.7 Plus,距 Qwen 3.7 Max 上线刚好 11 天。同样的 1M 上下文,同样的 35 小时自治上限。但价格才是头条:Plus 是 0.40/M输入,Max是 2.50/M——便宜约 6 倍——并且还能看图、看视频。Vision Arena 上 Plus 已经排到 #16。所以这周真正值得讨论的问题不是”要不要为视觉能力买单”,而是”Max 凭什么用 6 倍价格换来 2 个百分点的 benchmark 领先”。
|
4天前
|
存储 人工智能 数据可视化
别再手动复制 Skill 了:多 Agent 时代的 Skill 管理方案
多 Agent 场景下 Skill 的统一管理与同步。
231 124
|
11天前
|
缓存 人工智能 运维
GLM 5.2自托管全流程实战:硬件选型、vLLM/SGLang部署与成本盈亏测算
2026年智谱发布GLM 5.2超大混合专家模型,区别于以往仅开放API的闭源大模型,该模型权重以MIT开源协议对外发布,企业与开发者可完整下载、本地审计、私有化部署,实现数据不出环境、自定义微调、自主调度推理资源。GLM 5.2拥有753B总参数,原生支持百万级上下文窗口,在代码生成、长文档推理、数学逻辑等多项基准测试中对标国际顶尖商用模型,是首款可完整自托管的前沿代码向大模型。
876 0
|
4天前
|
SQL 存储 运维
日志能不能改?SLS LogStore 原生支持更新和删除了
随着日志承载的业务语义越来越多,数据订正、回填、清理等需求变得越来越常见。SLS 现已为 LogStore 提供原生 update/delete 能力——支持按 RowID 精确修改,按查询条件批量操作,类似计费调账、标签刷新、反馈回填等场景都可以直接在 LogStore 内完成闭环。
200 124