2025-07-16

第1章:Scrapy 爬虫框架基础与核心机制详解

1.1 什么是 Scrapy?

Scrapy 是一个开源的 Python 爬虫框架,用于从网站抓取数据,并可自动处理请求、提取、清洗和存储流程。它以异步事件驱动为核心,具备高性能、模块化、易扩展的特点。

✅ Scrapy 的核心优势

  • 异步非阻塞架构:基于 Twisted 网络库
  • 可扩展中间件机制:支持请求、响应、异常等各类钩子
  • 强大的选择器系统:XPath、CSS、正则混合使用
  • 支持分布式和断点续爬
  • 天然支持 Pipeline、Item 结构化存储

1.2 Scrapy 项目结构详解

一个 Scrapy 项目初始化结构如下:

$ scrapy startproject mycrawler
mycrawler/
├── mycrawler/               # 项目本体
│   ├── __init__.py
│   ├── items.py             # 定义数据结构
│   ├── middlewares.py       # 中间件处理
│   ├── pipelines.py         # 数据处理
│   ├── settings.py          # 配置文件
│   └── spiders/             # 爬虫脚本
│       └── example_spider.py
└── scrapy.cfg               # 项目配置入口

1.3 Scrapy 的核心执行流程

Scrapy 的执行流程如下图所示:

flowchart TD
    start(开始爬取) --> engine[Scrapy引擎]
    engine --> scheduler[调度器 Scheduler]
    scheduler --> downloader[下载器 Downloader]
    downloader --> middleware[下载中间件]
    middleware --> response[响应 Response]
    response --> spider[爬虫 Spider]
    spider --> item[Item 或 Request]
    item --> pipeline[Pipeline 处理]
    pipeline --> store[存储存入 DB/CSV/ES]
    spider --> engine

🔁 说明:

  • Engine 控制整个流程的数据流与调度;
  • Scheduler 实现任务排队去重;
  • Downloader 发出 HTTP 请求;
  • Spider 处理响应,提取数据或发起新的请求;
  • Pipeline 将数据持久化保存;
  • Middlewares 拦截每个阶段,可插拔增强功能。

1.4 一个最简单的 Scrapy Spider 示例

# spiders/example_spider.py
import scrapy

class ExampleSpider(scrapy.Spider):
    name = "example"
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        for quote in response.css('.quote'):
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('.author::text').get()
            }

        next_page = response.css('li.next a::attr(href)').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

✅ 输出结果(JSON):

{
  "text": "The world as we have created it is a process of our thinking.",
  "author": "Albert Einstein"
}

1.5 核心组件详解

组件功能说明
Spider编写解析逻辑parse() 为主入口
Item数据结构类似数据模型
Pipeline存储处理逻辑可入库、清洗、格式化
Downloader请求下载支持重试、UA、代理
Middleware请求/响应钩子插件式增强能力
Scheduler排队与去重支持断点续爬
Engine控制核心流程所有组件的桥梁

1.6 Request 与 Response 深度解析

yield scrapy.Request(
    url='https://example.com/page',
    callback=self.parse_page,
    headers={'User-Agent': 'CustomAgent'},
    meta={'retry': 3}
)
  • meta 字典可在请求中传递信息至下个响应;
  • dont_filter=True 表示不过滤重复请求。

1.7 XPath 与 CSS 选择器实战

# CSS 选择器
response.css('div.quote span.text::text').get()

# XPath
response.xpath('//div[@class="quote"]/span[@class="text"]/text()').get()
  • .get() 返回第一个结果;
  • .getall() 返回列表。

1.8 项目配置 settings.py 常用参数

BOT_NAME = 'mycrawler'
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 1
CONCURRENT_REQUESTS = 16
COOKIES_ENABLED = False
RETRY_ENABLED = True
  • 延迟访问:防止被封;
  • 关闭 Cookie:绕过某些反爬策略;
  • 并发控制:保证性能与安全。

1.9 数据持久化示例:Pipeline 到 CSV/MySQL/MongoDB

# pipelines.py
import csv

class CsvPipeline:
    def open_spider(self, spider):
        self.file = open('quotes.csv', 'w', newline='')
        self.writer = csv.writer(self.file)
        self.writer.writerow(['text', 'author'])

    def process_item(self, item, spider):
        self.writer.writerow([item['text'], item['author']])
        return item

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

1.10 调试技巧与日志配置

scrapy shell "https://quotes.toscrape.com"
# settings.py
LOG_LEVEL = 'DEBUG'
LOG_FILE = 'scrapy.log'

通过 shell 调试 XPath/CSS 表达式,可视化测试爬虫提取路径。


好的,以下是第2章:Scrapyd 服务化部署原理与实战的完整内容,已包含配置说明、API 示例、流程讲解和部署实战,直接复制即可使用:


第2章:Scrapyd 服务化部署原理与实战

2.1 什么是 Scrapyd?

Scrapyd 是一个专为 Scrapy 设计的爬虫部署服务,允许你将 Scrapy 爬虫“服务化”,并通过 HTTP API 实现远程启动、停止、部署和监控爬虫任务。

Scrapyd 核心作用是:将 Scrapy 脚本变为网络服务接口可以调度的“作业任务”,支持命令行或 Web 调度。

✅ Scrapyd 的主要能力包括:

  • 后台守护运行爬虫;
  • 支持多个项目的爬虫版本管理;
  • 提供完整的 HTTP 调度 API;
  • 输出日志、查看任务状态、取消任务;
  • 与 Gerapy、CI/CD 系统(如 Jenkins)无缝集成。

2.2 安装与快速启动

安装 Scrapyd

pip install scrapyd

启动 Scrapyd 服务

scrapyd

默认监听地址是 http://127.0.0.1:6800


2.3 Scrapyd 配置文件详解

默认配置路径:

  • Linux/macOS: ~/.scrapyd/scrapyd.conf
  • Windows: %APPDATA%\scrapyd\scrapyd.conf

示例配置文件内容:

[scrapyd]
bind_address = 0.0.0.0        # 允许外部访问
http_port = 6800
max_proc = 10                 # 最大并发爬虫数量
poll_interval = 5.0
logs_dir = logs
eggs_dir = eggs
dbs_dir = dbs

你可以手动创建这个文件并重启 Scrapyd。


2.4 创建 setup.py 以支持打包部署

Scrapyd 需要项目打包为 .egg 文件。首先在项目根目录创建 setup.py 文件:

from setuptools import setup, find_packages

setup(
    name='mycrawler',
    version='1.0',
    packages=find_packages(),
    entry_points={'scrapy': ['settings = mycrawler.settings']},
)

然后执行:

python setup.py bdist_egg

会在 dist/ 目录生成 .egg 文件,例如:

dist/
└── mycrawler-1.0-py3.10.egg

2.5 上传项目到 Scrapyd

通过 API 上传:

curl http://localhost:6800/addversion.json \
  -F project=mycrawler \
  -F version=1.0 \
  -F egg=@dist/mycrawler-1.0-py3.10.egg

上传成功返回示例:

{
  "status": "ok",
  "spiders": 3
}

2.6 启动爬虫任务

调用 API 启动任务:

curl http://localhost:6800/schedule.json \
  -d project=mycrawler \
  -d spider=example

Python 调用:

import requests

resp = requests.post("http://localhost:6800/schedule.json", data={
    "project": "mycrawler",
    "spider": "example"
})
print(resp.json())

返回:

{"status": "ok", "jobid": "abcde123456"}

2.7 查询任务状态

Scrapyd 提供三个任务队列:

  • pending:等待中
  • running:执行中
  • finished:已完成

查看所有任务状态:

curl http://localhost:6800/listjobs.json?project=mycrawler

返回结构:

{
  "status": "ok",
  "pending": [],
  "running": [],
  "finished": [
    {
      "id": "abc123",
      "spider": "example",
      "start_time": "2025-07-16 10:12:00",
      "end_time": "2025-07-16 10:13:10"
    }
  ]
}

2.8 停止任务

停止指定 job:

curl http://localhost:6800/cancel.json -d project=mycrawler -d job=abc123

2.9 查看可用爬虫、项目、版本

# 查看所有项目
curl http://localhost:6800/listprojects.json

# 查看项目的爬虫列表
curl http://localhost:6800/listspiders.json?project=mycrawler

# 查看项目的所有版本
curl http://localhost:6800/listversions.json?project=mycrawler

2.10 日志文件结构与查看方式

Scrapyd 默认日志路径为:

logs/
└── mycrawler/
    └── example/
        └── abc123456.log

查看日志:

tail -f logs/mycrawler/example/abc123456.log

也可以通过 Gerapy 提供的 Web UI 远程查看。


2.11 多节点部署与调度建议

在生产环境中,可以将 Scrapyd 安装在多台爬虫服务器上实现分布式调度。

部署建议:

  • 多台机器相同配置(Python 环境、Scrapy 项目结构一致);
  • 统一使用 Gerapy 作为调度平台;
  • 项目统一使用 CI/CD 工具(如 Jenkins)上传 egg;
  • 使用 Nginx 或其他服务网关统一管理多个 Scrapyd 节点;
  • 日志通过 ELK 或 Loki 系统集中分析。

2.12 常见问题与解决方案

问题说明解决方案
上传失败version 重复升级版本号或删除旧版本
无法访问IP 被限制bind\_address 配置为 0.0.0.0
启动失败egg 配置错误检查 entry_points 设置
运行失败环境不一致统一 Python 环境版本、依赖

第3章:Gerapy:可视化调度管理平台详解

3.1 Gerapy 是什么?

Gerapy 是由 Scrapy 官方衍生的开源项目,提供了一个 Web 管理面板,用于控制多个 Scrapyd 节点,实现爬虫任务可视化管理、项目上传、定时调度、日志查看等功能。

✅ Gerapy 的核心能力包括:

  • 多节点 Scrapyd 管理(分布式支持);
  • 爬虫项目在线上传、更新;
  • 可视化任务调度器;
  • 日志在线查看与状态监控;
  • 多人协作支持。

3.2 安装与环境准备

1. 安装 Gerapy

pip install gerapy

建议安装在独立虚拟环境中,并确保 Python 版本在 3.7 以上。

2. 初始化 Gerapy 项目

gerapy init    # 创建 gerapy 项目结构
cd gerapy
gerapy migrate  # 初始化数据库
gerapy createsuperuser  # 创建管理员账户

3. 启动 Gerapy 服务

gerapy runserver 0.0.0.0:8000

访问地址:

http://localhost:8000

3.3 项目结构介绍

gerapy/
├── projects/         # 本地 Scrapy 项目目录
├── db.sqlite3        # SQLite 存储
├── logs/             # 日志缓存
├── templates/        # Gerapy Web 模板
├── scrapyd_servers/  # 配置的 Scrapyd 节点
└── manage.py

3.4 添加 Scrapyd 节点

  1. 打开 Gerapy 页面(http://localhost:8000);
  2. 进入【节点管理】界面;
  3. 点击【添加节点】,填写信息:
字段示例值
名称本地节点
地址http://127.0.0.1:6800
描述本地测试 Scrapyd 服务
  1. 点击保存,即可自动测试连接。

3.5 上传 Scrapy 项目至 Scrapyd 节点

步骤:

  1. 将你的 Scrapy 项目放入 gerapy/projects/ 目录;
  2. 在【项目管理】页面点击【上传】;
  3. 选择节点(支持多节点)和版本号;
  4. 自动打包 .egg 并上传至目标 Scrapyd。

打包构建日志示例:

[INFO] Packing project: quotes_spider
[INFO] Generated egg: dist/quotes_spider-1.0-py3.10.egg
[INFO] Uploading to http://127.0.0.1:6800/addversion.json
[INFO] Upload success!

3.6 任务调度与自动运行

点击【任务调度】模块:

  • 创建任务(选择节点、爬虫、项目、调度周期);
  • 支持 Cron 表达式,例如:
表达式含义
* * * * *每分钟执行一次
0 0 * * *每天 0 点执行
0 8 * * 1每周一 8 点执行

可以设定参数、任务间隔、日志保存策略等。


3.7 在线日志查看

每个任务完成后,可直接在 Web 页面查看其对应日志,示例:

[INFO] Spider opened
[INFO] Crawled (200) <GET https://quotes.toscrape.com> ...
[INFO] Spider closed (finished)

点击日志详情可查看每一行详细输出,支持下载。


3.8 用户系统与权限管理

Gerapy 使用 Django 的 Auth 模块支持用户认证:

gerapy createsuperuser

也可以通过 Admin 页面创建多个用户、设定权限组,便于团队协作开发。


3.9 Gerapy 后台管理(Django Admin)

访问 http://localhost:8000/admin/ 使用管理员账户登录,可对以下内容进行管理:

  • 用户管理
  • Scrapyd 节点
  • 项目上传记录
  • 调度任务表
  • Cron 调度历史

3.10 高级特性与插件扩展

功能实现方式描述
节点负载均衡多节点轮询调度节点状态可扩展监控指标
数据可视化自定义报表模块与 matplotlib/pyecharts 集成
日志采集接入 ELK/Loki更强大的日志监控能力
自动构建部署GitLab CI/Jenkins支持自动化更新 Scrapy 项目并部署

3.11 Gerapy 与 Scrapyd 关系图解

graph TD
    U[用户操作界面] --> G[Gerapy Web界面]
    G --> S1[Scrapyd 节点 A]
    G --> S2[Scrapyd 节点 B]
    G --> Projects[本地 Scrapy 项目]
    G --> Cron[定时任务调度器]
    S1 --> Logs1[日志/状态]
    S2 --> Logs2[日志/状态]

3.12 常见问题处理

问题原因解决方案
上传失败egg 打包错误检查 setup.py 配置与版本
节点连接失败IP 被防火墙阻止修改 Scrapyd 配置为 0.0.0.0
爬虫未显示项目未上传成功确保项目可运行并打包正确
日志无法查看目录权限不足检查 logs 目录权限并重启服务

第4章:项目结构设计:从模块划分到任务封装

4.1 为什么要重构项目结构?

Scrapy 默认生成的项目结构非常基础,适合快速开发单个爬虫,但在实际业务中通常存在以下问题:

  • 多个爬虫文件之间高度重复;
  • 无法共用下载中间件或通用处理逻辑;
  • Pipeline、Item、Spider 无法复用;
  • 调度逻辑零散,不易维护;
  • 缺乏模块化与自动任务封装能力。

因此,我们需要一个更具层次化、组件化的架构。


4.2 推荐项目结构(模块化目录)

mycrawler/
├── mycrawler/                  # 项目主目录
│   ├── __init__.py
│   ├── items/                  # 所有 item 定义模块化
│   │   ├── __init__.py
│   │   └── quote_item.py
│   ├── pipelines/              # pipeline 分模块
│   │   ├── __init__.py
│   │   └── quote_pipeline.py
│   ├── middlewares/           # 通用中间件
│   │   ├── __init__.py
│   │   └── ua_rotate.py
│   ├── spiders/                # 各爬虫模块
│   │   ├── __init__.py
│   │   └── quote_spider.py
│   ├── utils/                  # 公共工具函数
│   │   └── common.py
│   ├── commands/               # 自定义命令(封装入口)
│   │   └── run_task.py
│   ├── scheduler/              # 任务调度逻辑封装
│   │   └── task_manager.py
│   ├── settings.py             # Scrapy 配置
│   └── main.py                 # 主启动入口(本地测试用)
├── scrapy.cfg
└── requirements.txt

这种结构有如下优势:

  • 每一层关注单一职责;
  • 逻辑复用更容易管理;
  • 支持 CI/CD 和自动测试集成;
  • 可以作为服务打包。

4.3 多爬虫设计与代码复用技巧

在 Spider 中实现通用基类:

# spiders/base_spider.py
import scrapy

class BaseSpider(scrapy.Spider):
    custom_settings = {
        'DOWNLOAD_DELAY': 1,
        'CONCURRENT_REQUESTS': 8,
    }

    def log_info(self, message):
        self.logger.info(f"[{self.name}] {message}")

继承该基类:

# spiders/quote_spider.py
from mycrawler.spiders.base_spider import BaseSpider

class QuoteSpider(BaseSpider):
    name = 'quote'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        for q in response.css('div.quote'):
            yield {
                'text': q.css('span.text::text').get(),
                'author': q.css('.author::text').get()
            }

4.4 Items 模块封装

统一管理所有 Item,便于维护与共享:

# items/quote_item.py
import scrapy

class QuoteItem(scrapy.Item):
    text = scrapy.Field()
    author = scrapy.Field()

4.5 Pipelines 分模块处理

模块化每类 pipeline,配置在 settings.py 中动态启用:

# pipelines/quote_pipeline.py
class QuotePipeline:
    def process_item(self, item, spider):
        item['text'] = item['text'].strip()
        return item

配置使用:

ITEM_PIPELINES = {
    'mycrawler.pipelines.quote_pipeline.QuotePipeline': 300,
}

4.6 通用中间件封装

通用代理、UA、异常处理:

# middlewares/ua_rotate.py
import random

class UARotateMiddleware:
    USER_AGENTS = [
        'Mozilla/5.0 (Windows NT 10.0; Win64)',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
    ]

    def process_request(self, request, spider):
        request.headers['User-Agent'] = random.choice(self.USER_AGENTS)

配置启用:

DOWNLOADER_MIDDLEWARES = {
    'mycrawler.middlewares.ua_rotate.UARotateMiddleware': 543,
}

4.7 utils:封装通用函数与解析器

# utils/common.py
from hashlib import md5

def generate_id(text):
    return md5(text.encode('utf-8')).hexdigest()

在 Spider 或 Pipeline 中调用:

from mycrawler.utils.common import generate_id

4.8 调度模块:scheduler/task\_manager.py

集中封装所有爬虫任务的调度管理:

import requests

class TaskManager:
    SCRAPYD_HOST = 'http://localhost:6800'

    @staticmethod
    def start_task(project, spider, version='default'):
        url = f"{TaskManager.SCRAPYD_HOST}/schedule.json"
        data = {'project': project, 'spider': spider}
        return requests.post(url, data=data).json()

4.9 自定义命令入口(封装脚本执行)

# commands/run_task.py
from scrapy.commands import ScrapyCommand
from mycrawler.scheduler.task_manager import TaskManager

class Command(ScrapyCommand):
    requires_project = True

    def short_desc(self):
        return "Run spider task by name"

    def add_options(self, parser):
        ScrapyCommand.add_options(self, parser)
        parser.add_option("--spider", dest="spider")

    def run(self, args, opts):
        spider = opts.spider
        if not spider:
            self.exitcode = 1
            self.stderr.write("Spider name is required")
        else:
            result = TaskManager.start_task("mycrawler", spider)
            self.stdout.write(f"Task Result: {result}")

4.10 main.py:本地开发调试入口

# main.py
from scrapy.cmdline import execute

if __name__ == '__main__':
    execute(['scrapy', 'crawl', 'quote'])

第5章:分布式爬虫部署:Docker + Scrapyd 多节点架构实战

5.1 为什么需要分布式爬虫?

在大型爬虫场景中,单台机器资源有限,且运行不稳定。因此,我们需要:

  • 多节点部署提升并发吞吐;
  • 弹性调度、自动容灾;
  • 节点间分摊负载,减少爬虫 IP 被封风险;
  • 与 Gerapy 联动统一管理。

5.2 Scrapyd 多节点部署原理图

graph TD
    G[Gerapy UI 管理平台]
    G --> N1[Scrapyd Node 1]
    G --> N2[Scrapyd Node 2]
    G --> N3[Scrapyd Node 3]
    N1 -->|任务调度| Spider1
    N2 -->|任务调度| Spider2
    N3 -->|任务调度| Spider3

说明:

  • Gerapy 控制多个 Scrapyd 实例;
  • Scrapyd 通过 HTTP 接口接收指令;
  • 每个 Scrapyd 节点可并发运行多个任务。

5.3 构建 Scrapyd 的 Docker 镜像

我们使用官方推荐方式制作 Scrapyd 镜像。

编写 Dockerfile:

FROM python:3.10-slim

RUN pip install --no-cache-dir scrapyd

EXPOSE 6800

CMD ["scrapyd"]

构建镜像:

docker build -t scrapyd-node:latest .

5.4 使用 Docker Compose 启动多个节点

创建 docker-compose.yml 文件:

version: '3'
services:
  scrapyd1:
    image: scrapyd-node:latest
    ports:
      - "6801:6800"
    container_name: scrapyd-node-1

  scrapyd2:
    image: scrapyd-node:latest
    ports:
      - "6802:6800"
    container_name: scrapyd-node-2

  scrapyd3:
    image: scrapyd-node:latest
    ports:
      - "6803:6800"
    container_name: scrapyd-node-3

启动容器:

docker-compose up -d

三个节点地址分别为:


5.5 上传项目至多个 Scrapyd 节点

可以使用 Gerapy 或命令行依次上传:

curl http://localhost:6801/addversion.json -F project=mycrawler -F version=1.0 -F egg=@dist/mycrawler.egg
curl http://localhost:6802/addversion.json -F project=mycrawler -F version=1.0 -F egg=@dist/mycrawler.egg
curl http://localhost:6803/addversion.json -F project=mycrawler -F version=1.0 -F egg=@dist/mycrawler.egg

5.6 任务调度至不同节点

在 Gerapy 中添加多个节点:

名称地址
节点1http://localhost:6801
节点2http://localhost:6802
节点3http://localhost:6803

然后你可以手动或定时调度任务给不同 Scrapyd 节点。


5.7 日志统一采集方案(可选)

每个 Scrapyd 节点会产生日志文件,结构如下:

/logs
└── mycrawler/
    └── spider1/
        └── jobid123.log

统一日志的方式:

  • 使用 docker volume 将日志挂载到宿主机;
  • 配置 Filebeat 采集日志 → 推送到 Logstash → Elasticsearch;
  • 使用 Grafana / Kibana 实时查看爬虫运行状态。

5.8 部署架构图

graph TD
    CI[CI/CD 构建服务] --> Upload[构建 egg 上传]
    Upload --> S1[Scrapyd 6801]
    Upload --> S2[Scrapyd 6802]
    Upload --> S3[Scrapyd 6803]

    Gerapy[Gerapy Web调度] --> S1
    Gerapy --> S2
    Gerapy --> S3

    Logs[日志采集模块] --> ELK[(ELK / Loki)]

5.9 扩展方案:使用 Nginx 统一入口

为避免暴露多个端口,可通过 Nginx 路由:

server {
    listen 80;

    location /scrapyd1/ {
        proxy_pass http://localhost:6801/;
    }

    location /scrapyd2/ {
        proxy_pass http://localhost:6802/;
    }
}

在 Gerapy 中填入统一的 Nginx 地址即可。


5.10 多节点调度策略建议

策略说明
轮询按顺序分配给每个节点
随机随机选择可用节点
权重给不同节点设置执行优先级
压力感知调度根据节点负载自动选择

Gerapy 默认是手动选择节点,也可二次开发支持智能调度。


第6章:Gerapy 自动调度任务系统原理与二次开发实践

6.1 Gerapy 的调度系统概览

Gerapy 使用 Django + APScheduler 构建定时任务系统:

  • 任务创建:前端设置任务 → 写入数据库;
  • 调度启动:后台定时器读取任务 → 调用 Scrapyd;
  • 任务状态:通过 job\_id 追踪 → 获取日志、标记完成;
  • 任务失败:默认不自动重试,需要扩展;

系统组件图:

graph TD
    User[用户设置任务] --> Gerapy[Web UI]
    Gerapy --> DB[任务数据库]
    Gerapy --> APS[APScheduler 后台调度器]
    APS --> Scrapyd[任务调度 Scrapyd]
    Scrapyd --> JobLog[日志 & 状态返回]

6.2 数据库结构分析(SQLite)

Gerapy 使用 SQLite 存储任务信息,相关核心模型位于:

  • tasks.models.Task
  • tasks.models.Schedule

表结构核心字段:

字段说明
name任务名称
project项目名称(上传时指定)
spider爬虫名称
nodeScrapyd 节点地址
croncron 表达式(调度周期)
args传参 JSON 字符串
enabled是否启用该任务
last_run_time上次运行时间

6.3 创建定时任务的完整流程

1. 上传项目至节点

上传成功后才能被调度系统识别。

2. 在 Web UI 配置任务

填写如下字段:

  • 项目名称(下拉选择)
  • 爬虫名称(自动识别)
  • cron 表达式(定时策略)
  • 参数(如时间范围、城市名等)

3. 后台调度器启动任务

Gerapy 启动后,会开启一个 APScheduler 后台守护线程,读取任务表并解析 cron 表达式,自动调度任务:

from apscheduler.schedulers.background import BackgroundScheduler

6.4 调度源码分析

任务调度核心在:

gerapy/server/tasks/scheduler.py

def run_task(task):
    url = task.node_url + "/schedule.json"
    data = {
        'project': task.project,
        'spider': task.spider,
        **task.args  # 支持动态传参
    }
    requests.post(url, data=data)

支持动态参数扩展,建议在表中将 args 以 JSON 存储并转换为字典发送。


6.5 自定义重试逻辑(任务失败处理)

Scrapyd 默认不提供任务失败回调,Gerapy 原始实现也没有失败检测。我们可以手动添加失败处理逻辑。

步骤:

  1. 每次调用任务后记录 job\_id;
  2. 定时调用 /listjobs.json?project=xxx 获取状态;
  3. 若任务超时/失败,可自动重试:
def check_and_retry(task):
    job_id = task.last_job_id
    status = get_job_status(job_id)
    if status == 'failed':
        run_task(task)  # 重新调度

可以将任务状态持久化存入数据库,做失败告警通知。


6.6 实现多参数任务支持(带动态参数)

原始 Web 配置只支持静态参数:

我们可以修改前端任务配置表单,添加参数输入框,并将 JSON 转为字典:

{
  "city": "shanghai",
  "category": "news"
}

后端接收到后:

import json

args_dict = json.loads(task.args)
data = {
    'project': task.project,
    'spider': task.spider,
    **args_dict
}

6.7 自定义任务运行监控界面

在 Gerapy 的管理后台添加任务状态查看:

  • 展示任务执行时间、状态;
  • 增加“运行日志查看按钮”;
  • 增加任务失败次数统计;
  • 可导出为 Excel 报表。

修改方式:

  • 模板:templates/tasks/index.html
  • 后端:tasks/views.py

6.8 与 Scrapyd 的调度通信优化建议

Scrapyd 无法主动回调任务状态,建议:

  • 每隔 60 秒轮询 /listjobs.json
  • 把状态写入本地数据库

也可以集成 Redis + Celery 实现任务链式调度:

@app.task
def monitor_job(job_id):
    status = scrapyd_api.get_status(job_id)
    if status == 'finished':
        do_next_step()
    elif status == 'failed':
        retry_task(job_id)

6.9 图解:任务调度生命周期

sequenceDiagram
    participant User
    participant Gerapy
    participant DB
    participant APScheduler
    participant Scrapyd

    User->>Gerapy: 提交任务 + Cron
    Gerapy->>DB: 写入任务数据
    APScheduler->>DB: 周期性读取任务
    APScheduler->>Scrapyd: 发起任务调度
    Scrapyd-->>Gerapy: 返回 JobID
    Gerapy->>DB: 记录状态

    loop 每60秒
        Gerapy->>Scrapyd: 查询任务状态
        Scrapyd-->>Gerapy: 状态返回
        Gerapy->>DB: 更新任务结果
    end

6.10 Gerapy 二次开发扩展清单

扩展模块功能描述
任务失败自动重试若任务失败,自动重调
参数模板支持每种 Spider 有预设参数模板
任务依赖调度支持“任务完成 → 触发下个任务”
日志分析统计抓取量、成功率、错误数
通知系统邮件、钉钉、飞书推送失败通知

第7章:Gerapy + Jenkins 构建自动化爬虫发布与持续集成系统

7.1 为什么需要自动化发布?

在大型爬虫团队中,频繁的代码更新和项目部署是常态,手动上传、调度存在以下弊端:

  • 易出错,流程繁琐;
  • 发布不及时,影响数据时效;
  • 无法保障多节点版本一致;
  • 缺乏任务执行的自动反馈。

基于 Jenkins 的自动化 CI/CD 流程,结合 Gerapy 统一管理,实现“代码提交 → 自动构建 → 自动部署 → 自动调度”的闭环,极大提高效率和可靠性。


7.2 Jenkins 环境搭建与配置

1. 安装 Jenkins

官方提供多平台安装包,Docker 方式也很方便:

docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins:lts

2. 安装插件

  • Git 插件(源码管理)
  • Pipeline 插件(流水线)
  • SSH 插件(远程命令)
  • HTTP Request 插件(API 调用)

7.3 Git 代码管理规范

建议每个爬虫项目维护独立 Git 仓库,分支策略:

  • master/main:稳定版
  • dev:开发版
  • Feature 分支:新功能开发

7.4 Jenkins Pipeline 脚本示例

pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                git branch: 'master', url: 'git@github.com:username/mycrawler.git'
            }
        }
        stage('Install Dependencies') {
            steps {
                sh 'pip install -r requirements.txt'
            }
        }
        stage('Build Egg') {
            steps {
                sh 'python setup.py bdist_egg'
            }
        }
        stage('Upload to Scrapyd') {
            steps {
                script {
                    def eggPath = "dist/mycrawler-1.0-py3.10.egg"
                    def response = httpRequest httpMode: 'POST', 
                        url: 'http://scrapyd-server:6800/addversion.json', 
                        multipartFormData: [
                            [name: 'project', contents: 'mycrawler'],
                            [name: 'version', contents: '1.0'],
                            [name: 'egg', file: eggPath]
                        ]
                    echo "Upload Response: ${response.content}"
                }
            }
        }
        stage('Trigger Spider') {
            steps {
                httpRequest httpMode: 'POST', url: 'http://scrapyd-server:6800/schedule.json', body: 'project=mycrawler&spider=quote', contentType: 'APPLICATION_FORM'
            }
        }
    }

    post {
        failure {
            mail to: 'team@example.com',
                 subject: "Jenkins Build Failed: ${env.JOB_NAME}",
                 body: "Build failed. Please check Jenkins."
        }
    }
}

7.5 与 Gerapy 的结合

  • Jenkins 只负责代码构建与上传;
  • Gerapy 负责任务调度、状态管理与日志展示;
  • 结合 Gerapy 提供的 API,可实现更加灵活的任务管理;

7.6 自动化部署流程图

graph LR
    Git[Git Push] --> Jenkins
    Jenkins --> Egg[构建 Egg]
    Egg --> Upload[上传至 Scrapyd]
    Upload --> Gerapy
    Gerapy --> Schedule[调度任务]
    Schedule --> Scrapyd
    Scrapyd --> Logs[日志收集]

7.7 常见问题与排查

问题可能原因解决方案
上传失败版本号重复或权限不足增加版本号,检查 Scrapyd 权限
任务启动失败参数错误或节点未注册检查参数,确认 Scrapyd 状态
Jenkins 执行超时网络慢或命令卡住调整超时,检查网络和依赖
邮件通知未发送邮箱配置错误或 Jenkins 插件缺失配置 SMTP,安装邮件插件

7.8 实战示例:多项目多节点自动发布

1. 在 Jenkins 中创建多项目流水线,分别对应不同爬虫;

2. 使用参数化构建,动态指定项目名称与版本号;

3. 脚本自动上传对应节点,保证多节点版本一致;

4. 调用 Gerapy API 自动创建调度任务并启用。


7.9 安全性建议

  • Jenkins 访问限制 IP 白名单;
  • Scrapyd 绑定内网地址,避免暴露公网;
  • API 接口添加 Token 校验;
  • 代码仓库权限管理。

第8章:Scrapy 项目性能调优与异步下载深度解析

8.1 Scrapy 异步架构简介

Scrapy 基于 Twisted 异步网络框架,实现高效的网络 I/O 处理。

关键特点:

  • 非阻塞 I/O,避免线程切换开销;
  • 单线程并发处理,降低资源消耗;
  • 通过事件循环管理请求和响应。

8.2 Twisted 核心概念

  • Reactor:事件循环核心,负责调度 I/O 事件;
  • Deferred:异步结果占位符,回调机制实现链式操作;
  • ProtocolTransport:网络通信协议和数据传输抽象。

8.3 Scrapy 下载流程

sequenceDiagram
    participant Spider
    participant Scheduler
    participant Downloader
    participant Reactor

    Spider->>Scheduler: 发送请求Request
    Scheduler->>Downloader: 获取请求
    Downloader->>Reactor: 非阻塞发起请求
    Reactor-->>Downloader: 请求完成,接收响应Response
    Downloader->>Scheduler: 返回响应
    Scheduler->>Spider: 分发Response给回调函数

8.4 关键性能影响点

影响因素说明
并发请求数CONCURRENT_REQUESTS 设置
下载延迟DOWNLOAD_DELAY 控制访问频率
下载超时DOWNLOAD_TIMEOUT 影响响应等待时长
DNS 解析DNS 缓存配置减少解析开销
中间件处理自定义中间件效率影响整体性能

8.5 配置参数优化建议

# settings.py
CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 16
DOWNLOAD_DELAY = 0.25
DOWNLOAD_TIMEOUT = 15
REACTOR_THREADPOOL_MAXSIZE = 20
DNSCACHE_ENABLED = True
  • CONCURRENT_REQUESTS 控制全局并发数,适当调高提升吞吐;
  • DOWNLOAD_DELAY 设置合理延迟,避免被封禁;
  • REACTOR_THREADPOOL_MAXSIZE 控制线程池大小,影响 DNS 和文件 I/O。

8.6 异步下载中间件示例

编写下载中间件,实现异步请求拦截:

from twisted.internet.defer import Deferred
from twisted.web.client import Agent

class AsyncDownloaderMiddleware:

    def process_request(self, request, spider):
        d = Deferred()
        agent = Agent(reactor)
        agent.request(b'GET', request.url.encode('utf-8')).addCallback(self.handle_response, d)
        return d

    def handle_response(self, response, deferred):
        # 处理响应,构建 Scrapy Response
        scrapy_response = ...
        deferred.callback(scrapy_response)

8.7 高性能爬虫案例分析

案例:大规模商品信息抓取

  • 使用 CONCURRENT_REQUESTS=64 提升爬取速度;
  • 实现基于 Redis 的请求去重和分布式调度;
  • 自定义下载中间件过滤无效请求;
  • 结合异步数据库写入,减少阻塞。

8.8 CPU 与内存监控与调优

  • 监控爬虫运行时 CPU、内存占用,排查内存泄漏;
  • 优化 Item Pipeline,减少阻塞操作;
  • 合理使用 Scrapy Signals 做性能统计。

8.9 避免常见性能陷阱

陷阱说明解决方案
同步阻塞调用阻塞数据库、文件写入使用异步写入或线程池
过多下载延迟误用高延迟导致吞吐降低调整合理下载间隔
大量小任务导致调度开销任务拆分不合理,调度压力大合并任务,批量处理
DNS 解析瓶颈每次请求都进行 DNS 解析开启 DNS 缓存

8.10 图解:Scrapy 异步事件流

flowchart TD
    Start[爬虫启动]
    Start --> RequestQueue[请求队列]
    RequestQueue --> Reactor[Twisted Reactor事件循环]
    Reactor --> Downloader[异步下载器]
    Downloader --> ResponseQueue[响应队列]
    ResponseQueue --> Spider[爬虫解析]
    Spider --> ItemPipeline[数据处理管道]
    ItemPipeline --> Store[存储数据库]
    Spider --> RequestQueue

第9章:Scrapy 多源异步分布式爬虫设计与实战

9.1 多源爬取的挑战与需求

现代业务中,往往需要同时抓取多个网站或接口数据,面临:

  • 多数据源结构各异,解析复杂;
  • 任务数量大,调度难度提升;
  • 单机资源有限,需分布式部署;
  • 实时性和容错要求高。

9.2 架构设计原则

  • 模块化解析:针对不同数据源设计独立 Spider,复用基础组件;
  • 异步调度:利用 Scrapy + Twisted 异步提高效率;
  • 分布式调度:结合 Scrapyd 和 Gerapy 多节点管理;
  • 去重与存储统一:采用 Redis 等中间件实现请求去重和缓存,统一存储。

9.3 多源爬虫架构图

graph TD
    User[用户请求] --> Scheduler[调度系统]
    Scheduler --> ScrapydNode1[Scrapyd节点1]
    Scheduler --> ScrapydNode2[Scrapyd节点2]
    ScrapydNode1 --> Spider1[Spider-数据源A]
    ScrapydNode2 --> Spider2[Spider-数据源B]
    Spider1 --> Redis[请求去重 & 缓存]
    Spider2 --> Redis
    Spider1 --> DB[数据存储]
    Spider2 --> DB

9.4 Redis 实现请求去重与分布式队列

  • 使用 Redis set 实现请求 URL 去重,避免重复抓取;
  • 采用 Redis List 或 Stream 做任务队列,支持分布式消费;
  • 结合 scrapy-redis 插件实现分布式调度。

9.5 scrapy-redis 集成示例

# settings.py
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_URL = "redis://127.0.0.1:6379"

# spider.py
from scrapy_redis.spiders import RedisSpider

class MultiSourceSpider(RedisSpider):
    name = 'multi_source'
    redis_key = 'multi_source:start_urls'

    def parse(self, response):
        # 解析逻辑
        pass

9.6 异步处理与请求批量调度

  • 优化请求并发数,充分利用异步 I/O;
  • 实现请求批量提交,减少调度延迟;
  • 结合 Redis Stream 做消费记录,保障数据完整。

9.7 分布式爬虫运行监控方案

  • 利用 Gerapy 监控各节点任务状态;
  • 通过 ELK/Prometheus+Grafana 收集性能指标;
  • 实时告警系统保证故障快速响应。

9.8 多源爬虫实战案例

业务需求:

采集电商平台 A、新闻网站 B、社交平台 C 的数据。

实现步骤:

  1. 分别为 A、B、C 创建独立 Spider;
  2. 在 Redis 中维护不同队列和去重集合;
  3. 通过 Scrapyd 多节点分布部署,利用 Gerapy 统一调度;
  4. 监控日志并实时反馈任务运行情况。

9.9 容错设计与自动重试

  • 对失败请求做自动重试机制;
  • 利用 Redis 记录失败 URL 和次数,超过阈值报警;
  • 支持任务断点续爬。

9.10 图解:多源分布式异步爬虫数据流

flowchart LR
    Subgraph Redis
        A(RequestQueue)
        B(DupeFilterSet)
        C(FailQueue)
    end

    Spider1 -->|请求| A
    Spider2 -->|请求| A
    Spider1 -->|去重| B
    Spider2 -->|去重| B
    Spider1 -->|失败记录| C
    Spider2 -->|失败记录| C
    A --> ScrapydNodes
    ScrapydNodes --> DB

第10章:Scrapy 爬虫安全防护与反爬策略破解实战

10.1 反爬机制概述

网站常见反爬措施包括:

  • IP 封禁与限频;
  • User-Agent 及请求头检测;
  • Cookie 验证与登录校验;
  • JavaScript 渲染与动态内容加载;
  • CAPTCHA 验证码;
  • Honeypot 诱饵链接与数据陷阱。

10.2 IP 代理池构建与使用

10.2.1 代理池的重要性

  • 防止单 IP 访问被封;
  • 分散请求压力;
  • 模拟多地域访问。

10.2.2 免费与付费代理对比

类型优点缺点
免费代理易获取,成本低不稳定,速度慢
付费代理稳定高效,安全成本较高

10.2.3 代理池实现示例

import requests
import random

class ProxyPool:
    def __init__(self, proxies):
        self.proxies = proxies

    def get_random_proxy(self):
        return random.choice(self.proxies)

proxy_pool = ProxyPool([
    "http://111.111.111.111:8080",
    "http://222.222.222.222:8080",
    # 更多代理
])

def fetch(url):
    proxy = proxy_pool.get_random_proxy()
    response = requests.get(url, proxies={"http": proxy, "https": proxy})
    return response.text

10.3 User-Agent 及请求头伪装

  • 动态随机更换 User-Agent;
  • 模拟浏览器常用请求头;
  • 配合 Referer、防盗链头部。

示例:

import random

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...",
    # 更多 User-Agent
]

def get_random_headers():
    return {
        "User-Agent": random.choice(USER_AGENTS),
        "Accept-Language": "en-US,en;q=0.9",
        "Referer": "https://example.com"
    }

10.4 Cookie 管理与登录模拟

  • 自动维护 CookieJar,实现会话保持;
  • 使用 Scrapy 的 CookiesMiddleware
  • 模拟登录表单提交、Token 获取。

10.5 JavaScript 渲染处理

  • 使用 Selenium、Playwright 等浏览器自动化工具;
  • 结合 Splash 实现轻量级渲染;
  • Scrapy-Splash 集成示例。

10.6 CAPTCHA 验证码识别与绕过

  • 使用第三方打码平台(如超级鹰);
  • OCR 技术自动识别;
  • 结合滑动验证码、图片验证码破解技巧。

10.7 Honeypot 与数据陷阱识别

  • 分析页面结构,避免访问隐藏链接;
  • 验证数据合理性,过滤异常数据;
  • 增加数据校验逻辑。

10.8 反爬策略动态适应

  • 动态调整请求频率;
  • 智能代理切换;
  • 实时检测封禁并自动更换 IP。

10.9 实战案例:绕过某电商反爬

  1. 分析封禁策略,发现基于 IP 限制;
  2. 搭建稳定代理池,结合动态 User-Agent;
  3. 使用 Selenium 处理登录与 JS 渲染;
  4. 实现验证码自动识别与重试;
  5. 持续监控并调整请求参数。

10.10 图解:反爬防护与破解流程

flowchart TD
    Request[请求网站]
    subgraph 反爬防护
        IPCheck[IP限制]
        UACheck[User-Agent检测]
        JSRender[JS动态渲染]
        CAPTCHA[验证码验证]
        Honeypot[隐藏陷阱]
    end
    Request -->|绕过| ProxyPool[代理池]
    Request -->|伪装| Header[请求头伪装]
    Request -->|渲染| Browser[浏览器自动化]
    Request -->|验证码| OCR[验证码识别]

第11章:Scrapy+Redis+Kafka 实时分布式数据管道架构设计

11.1 现代数据采集的挑战

随着数据量和业务复杂度增长,传统单机爬虫难以满足:

  • 大规模数据实时采集;
  • 多源异步任务调度;
  • 高吞吐、低延迟数据处理;
  • 系统弹性和容错能力。

11.2 架构总体设计

本架构采用 Scrapy 作为采集引擎,Redis 负责调度和请求去重,Kafka 用于实时数据传输和处理。

graph LR
    Spider[Scrapy Spider] --> RedisQueue[Redis 请求队列]
    RedisQueue --> ScrapyScheduler[Scrapy Scheduler]
    ScrapyScheduler --> Downloader[Scrapy Downloader]
    Downloader --> Parser[Scrapy Parser]
    Parser --> KafkaProducer[Kafka 生产者]
    KafkaProducer --> KafkaCluster[Kafka 集群]
    KafkaCluster --> DataProcessor[实时数据处理]
    DataProcessor --> DataStorage[数据库/数据仓库]

11.3 Scrapy 与 Redis 集成

11.3.1 scrapy-redis 插件

  • 实现请求去重与分布式调度;
  • 支持请求缓存和持久化队列。

11.3.2 配置示例

# settings.py
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_URL = "redis://127.0.0.1:6379"

11.4 Kafka 在实时数据流中的角色

Kafka 是一个高吞吐、分布式消息系统,支持:

  • 多生产者、多消费者模型;
  • 持久化消息,支持回溯;
  • 实时流处理。

11.5 Scrapy 发送数据到 Kafka

利用 kafka-python 库,将爬取的 Item 实时发送到 Kafka:

from kafka import KafkaProducer
import json

producer = KafkaProducer(bootstrap_servers='localhost:9092')

class MyPipeline:
    def process_item(self, item, spider):
        data = json.dumps(dict(item)).encode('utf-8')
        producer.send('scrapy_topic', data)
        return item

11.6 Kafka 消费者与实时处理

  • 构建消费者服务读取 Kafka 数据;
  • 实时清洗、分析或存入数据库;
  • 支持扩展为 Flink、Spark Streaming 等流式计算平台。

11.7 架构优势

优点说明
高扩展性各组件独立,易横向扩展
异步高吞吐Redis + Kafka 保证数据流畅
容错能力消息持久化,失败可重试
灵活的数据消费模式支持多消费者并行处理

11.8 实战部署建议

  • Redis 集群配置,保证调度高可用;
  • Kafka 集群部署,分区合理设计;
  • Scrapy 多节点分布式部署,配合 Gerapy 调度;
  • 日志监控与报警。

11.9 图解:实时分布式数据流转

flowchart LR
    subgraph Scrapy集群
        A1[Spider1]
        A2[Spider2]
    end
    A1 --> RedisQueue
    A2 --> RedisQueue
    RedisQueue --> ScrapyScheduler
    ScrapyScheduler --> Downloader
    Downloader --> Parser
    Parser --> KafkaProducer
    KafkaProducer --> KafkaCluster
    KafkaCluster --> Consumer1
    KafkaCluster --> Consumer2
    Consumer1 --> DB1[数据库]
    Consumer2 --> DB2[数据仓库]

第12章:Scrapy 与机器学习结合实现智能化数据采集

12.1 智能爬虫的需求与优势

  • 自动识别和过滤无效数据,提高数据质量;
  • 动态调整爬取策略,实现精准采集;
  • 结合自然语言处理提取关键信息;
  • 实现异常检测与自动告警。

12.2 机器学习在爬虫中的应用场景

应用场景说明
数据分类与标注自动对爬取内容进行分类
内容去重基于相似度的文本去重
页面结构识别自动识别变动页面的内容区域
异常数据检测检测错误或异常数据
智能调度策略根据历史数据动态调整爬取频率

12.3 典型机器学习技术

  • 文本分类(SVM、深度学习模型);
  • 聚类分析(K-Means、DBSCAN);
  • 自然语言处理(NER、关键词抽取);
  • 机器视觉(图像识别);

12.4 Scrapy 集成机器学习示例

4.1 数据预处理 Pipeline

import joblib

class MLClassificationPipeline:

    def __init__(self):
        self.model = joblib.load('model.pkl')

    def process_item(self, item, spider):
        features = self.extract_features(item)
        pred = self.model.predict([features])
        item['category'] = pred[0]
        return item

    def extract_features(self, item):
        # 特征提取逻辑,如文本向量化
        return ...

12.5 动态调度与策略优化

  • 利用模型预测网页变化,自动调整调度频率;
  • 结合强化学习实现自适应调度。

12.6 智能内容提取

  • 利用 NLP 模型自动识别正文、标题、时间等;
  • 减少人工规则配置,提高适应性。

12.7 异常检测与自动告警

  • 训练模型检测异常页面或数据;
  • 爬虫实时反馈异常,自动暂停或重试。

12.8 图解:机器学习驱动的智能爬虫流程

flowchart TD
    Spider[Scrapy Spider]
    MLModel[机器学习模型]
    DataPreprocess[数据预处理]
    Scheduler[调度系统]
    Monitor[异常检测与告警]

    Spider --> DataPreprocess --> MLModel --> Scheduler
    MLModel --> Monitor
    Scheduler --> Spider

2024-11-30

Python Selenium 的安装和教程

Selenium 是一款强大的 Web 自动化测试工具,它可以用来模拟浏览器操作、爬取动态数据或进行自动化任务。本文将全面介绍 Selenium 的安装和基本使用,通过代码示例和图解帮助你快速上手。


一、Selenium 的安装

1.1 安装 Selenium 库

首先,你需要安装 selenium 库:

pip install selenium

1.2 下载 WebDriver

Selenium 需要配合浏览器驱动 (WebDriver) 一起使用,不同浏览器对应的驱动如下:

下载后将驱动程序添加到系统的环境变量 PATH 中。


二、Selenium 的基本使用

2.1 启动浏览器

示例代码

以下代码演示如何启动 Chrome 浏览器并打开百度主页:

from selenium import webdriver

# 设置 WebDriver 路径
driver_path = "path/to/chromedriver"  # 替换为实际路径
driver = webdriver.Chrome(executable_path=driver_path)

# 打开百度
driver.get("https://www.baidu.com")

# 打印页面标题
print("页面标题:", driver.title)

# 关闭浏览器
driver.quit()

输出示例

页面标题: 百度一下,你就知道

2.2 查找页面元素

Selenium 提供了多种方式查找页面元素:

  • ID: find_element_by_id
  • 类名: find_element_by_class_name
  • CSS选择器: find_element_by_css_selector
  • XPath: find_element_by_xpath

示例代码

from selenium import webdriver

driver = webdriver.Chrome(executable_path="path/to/chromedriver")
driver.get("https://www.baidu.com")

# 查找搜索框并输入文字
search_box = driver.find_element_by_id("kw")
search_box.send_keys("Python Selenium")

# 点击“百度一下”按钮
search_button = driver.find_element_by_id("su")
search_button.click()

# 打印当前页面 URL
print("当前页面 URL:", driver.current_url)

# 关闭浏览器
driver.quit()

2.3 模拟用户操作

示例代码:自动登录示例

以自动登录 GitHub 为例:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time

driver = webdriver.Chrome(executable_path="path/to/chromedriver")
driver.get("https://github.com/login")

# 输入用户名和密码
driver.find_element(By.ID, "login_field").send_keys("your_username")
driver.find_element(By.ID, "password").send_keys("your_password")

# 点击登录按钮
driver.find_element(By.NAME, "commit").click()

# 等待加载并打印登录结果
time.sleep(2)
print("登录成功" if "dashboard" in driver.current_url else "登录失败")

driver.quit()

三、常用功能示例

3.1 截屏功能

Selenium 可以截取页面截图:

driver.save_screenshot("screenshot.png")
print("截图已保存")

3.2 动态等待

在加载动态页面时,可以使用显式或隐式等待:

隐式等待

driver.implicitly_wait(10)  # 等待 10 秒

显式等待

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "some_id"))
)

3.3 滚动页面

滚动到页面底部:

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

3.4 处理弹窗

示例代码:关闭弹窗

alert = driver.switch_to.alert
print("弹窗内容:", alert.text)
alert.accept()  # 点击“确定”

3.5 爬取动态网页数据

Selenium 可以用于爬取 JavaScript 动态渲染的内容。例如:

driver.get("https://quotes.toscrape.com/js/")
quotes = driver.find_elements(By.CLASS_NAME, "quote")
for quote in quotes:
    print(quote.text)

四、完整示例:自动化搜索并截图

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

# 设置 WebDriver
driver = webdriver.Chrome(executable_path="path/to/chromedriver")

# 打开百度并搜索
driver.get("https://www.baidu.com")
search_box = driver.find_element(By.ID, "kw")
search_box.send_keys("Python Selenium 教程")
search_button = driver.find_element(By.ID, "su")
search_button.click()

# 等待加载完成并截图
time.sleep(2)
driver.save_screenshot("search_results.png")
print("搜索结果已截图保存")

# 关闭浏览器
driver.quit()

五、注意事项

  1. 浏览器版本匹配:确保 WebDriver 与浏览器的版本匹配,否则会报错。
  2. 反爬策略:很多网站对 Selenium 的行为进行检测,可以通过添加请求头或使用无头模式规避。
  3. 资源管理:使用完浏览器后务必调用 driver.quit() 释放资源。

六、总结

Selenium 是一个功能强大的工具,在 Web 自动化测试和动态数据抓取中有广泛应用。本文通过代码示例详细讲解了 Selenium 的基本用法及常见功能,希望能帮助你更高效地完成自动化任务。

如果想深入学习 Selenium,可以尝试结合 无头浏览器模式集成 pytest 框架 实现更复杂的应用!

2024-11-27

Python在网络爬虫和数据抓取中的应用

网络爬虫(Web Scraping)是从互联网上自动提取信息的技术。在 Python 中,网络爬虫通常用于抓取网站内容,如新闻、商品信息、评论等。Python 提供了许多强大的库来进行网页抓取和数据处理,比如 requestsBeautifulSoupSeleniumScrapy 等。

本文将详细介绍 Python 在网络爬虫和数据抓取中的应用,并通过代码示例、图解和详细说明,帮助你轻松理解和掌握这一技术。

一、网络爬虫的基本概念

网络爬虫是一种自动化程序,旨在模拟人工浏览网页,获取网页上的数据。它的基本工作流程如下:

  1. 发送请求:爬虫向目标网站发送 HTTP 请求,获取网页内容。
  2. 解析网页:获取到网页后,爬虫需要解析网页内容,提取其中的数据。
  3. 存储数据:将提取的数据保存到本地文件、数据库等。

二、Python爬虫开发的常用库

  1. requests:发送 HTTP 请求,获取网页内容。
  2. BeautifulSoup:解析 HTML 文档,提取其中的元素。
  3. Selenium:模拟浏览器操作,处理动态网页(JavaScript 渲染的网页)。
  4. Scrapy:一个用于大规模抓取的框架,适用于复杂的爬虫任务。

三、基本的网络爬虫实现:使用 requests 和 BeautifulSoup

1. 安装必要的库

首先,确保你安装了 requestsbeautifulsoup4,可以使用以下命令安装:

pip install requests beautifulsoup4

2. 发送请求并解析网页

假设我们想抓取一个网页的标题、链接等信息。以下是一个简单的爬虫示例:

import requests
from bs4 import BeautifulSoup

# 发送 GET 请求
url = 'https://quotes.toscrape.com/'
response = requests.get(url)

# 如果请求成功,解析 HTML 内容
if response.status_code == 200:
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # 提取网页中的所有引用
    quotes = soup.find_all('span', class_='text')
    authors = soup.find_all('small', class_='author')
    
    # 打印所有引用及其作者
    for quote, author in zip(quotes, authors):
        print(f'"{quote.text}" - {author.text}')
else:
    print(f"Failed to retrieve webpage. Status code: {response.status_code}")

代码解释:

  1. 发送请求requests.get(url) 发送 HTTP GET 请求来获取网页内容。
  2. 解析网页:使用 BeautifulSoup 解析 HTML 内容,指定解析器为 'html.parser'
  3. 提取数据:通过 soup.find_all() 方法提取所有符合条件的元素。例如,提取所有的引用 span 标签和作者 small 标签。
  4. 打印数据:通过 zip() 函数将引用和作者配对,输出每个引用及其对应的作者。

输出示例:

““The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”” - Albert Einstein
““It is our choices that show what we truly are, far more than our abilities.”” - J.K. Rowling
...

3. 图解爬虫流程

  • 发送请求:客户端向服务器发送 HTTP 请求,获取网页内容。
  • 解析网页:服务器返回 HTML 数据,爬虫利用 BeautifulSoup 对 HTML 进行解析,提取数据。
  • 提取数据:从 HTML 中提取需要的信息,如文本、链接等。
  • 存储数据:将提取的数据保存到文件或数据库中,便于后续分析。
+-----------------+
|  User Request   |
| (HTTP Request)  |
+-----------------+
        |
        v
+-----------------+
| Server Response |
| (HTML Content)  |
+-----------------+
        |
        v
+-----------------+
|   Parse HTML    |
| (BeautifulSoup)  |
+-----------------+
        |
        v
+-----------------+
|  Extract Data   |
|  (quotes, etc.) |
+-----------------+
        |
        v
+-----------------+
|   Store Data    |
|  (CSV, DB, etc.)|
+-----------------+

四、爬取动态网页:使用 Selenium

有些网页是通过 JavaScript 动态加载内容的,传统的 requestsBeautifulSoup 无法直接抓取这类内容。此时,可以使用 Selenium 来模拟浏览器的行为。

1. 安装 Selenium 和 WebDriver

首先,你需要安装 selenium 库,并下载一个 WebDriver(如 ChromeDriver)。可以通过以下命令安装 Selenium:

pip install selenium

下载并安装 ChromeDriver(或其他浏览器的驱动程序),然后将驱动程序路径添加到环境变量中。

2. 使用 Selenium 模拟浏览器

以下是一个使用 Selenium 抓取动态加载内容的示例:

from selenium import webdriver
from selenium.webdriver.common.by import By

# 设置 WebDriver,指定 Chrome 驱动
driver = webdriver.Chrome(executable_path='/path/to/chromedriver')

# 打开目标网页
url = 'https://quotes.toscrape.com/js/'
driver.get(url)

# 等待网页加载完成
driver.implicitly_wait(10)

# 获取网页中的引用和作者
quotes = driver.find_elements(By.CLASS_NAME, 'text')
authors = driver.find_elements(By.CLASS_NAME, 'author')

# 打印结果
for quote, author in zip(quotes, authors):
    print(f'"{quote.text}" - {author.text}')

# 关闭浏览器
driver.quit()

代码解释:

  1. 设置 WebDriver:使用 webdriver.Chrome() 启动 Chrome 浏览器并指定 ChromeDriver 的路径。
  2. 打开网页:使用 driver.get(url) 打开目标网页。
  3. 等待加载driver.implicitly_wait(10) 设置隐式等待,等待页面加载完成。
  4. 抓取数据:通过 driver.find_elements() 查找页面中的引用和作者。
  5. 打印数据:将抓取到的内容输出。

3. 使用 Selenium 的优缺点

  • 优点

    • 能够处理 JavaScript 动态渲染的网页。
    • 可以模拟用户操作(点击、滚动等)。
  • 缺点

    • 相较于 requests,速度较慢,因为它模拟了完整的浏览器操作。
    • 需要安装 WebDriver,配置较为复杂。

五、总结

通过本篇教程的学习,你已经掌握了如何使用 Python 进行网页抓取,并且理解了如何处理静态网页和动态网页。以下是你应该掌握的关键知识点:

  1. 请求网页:使用 requests 库发送 HTTP 请求,获取网页内容。
  2. 解析网页:使用 BeautifulSoup 解析网页内容,并提取需要的数据。
  3. 处理动态网页:使用 Selenium 模拟浏览器操作,抓取通过 JavaScript 渲染的内容。
  4. 存储数据:将抓取的数据保存到文件或数据库中,方便后续处理和分析。

希望本教程能够帮助你轻松上手 Python 爬虫,并在实际应用中获得良好的成果!

2024-11-27

Python 神器:一键下载 M3U8 并转换为 MP4

M3U8 是一种常见的媒体播放文件格式,通常用于视频流的播放,例如通过 HTTP Live Streaming (HLS) 协议传输的视频流。本文将介绍如何使用 Python 下载 M3U8 文件中的所有视频片段,并将它们合并为一个 MP4 文件。

一、前期准备

1. 安装依赖

我们需要使用几个 Python 库来实现下载和合并 M3U8 文件中的视频片段。最主要的库包括 m3u8(用于解析 M3U8 文件)和 ffmpeg(用于视频合并和转码)。同时,我们还需要 requests 库来下载 M3U8 文件中的 TS 视频片段。

首先,安装依赖库:

pip install m3u8 requests

安装 ffmpeg(用于视频处理):

  • 对于 Windows 用户,可以从 FFmpeg官网 下载并安装。
  • 对于 MacOS 用户,可以使用 Homebrew 安装:

    brew install ffmpeg
  • 对于 Linux 用户,可以使用 apt-get 安装:

    sudo apt install ffmpeg

二、M3U8 下载与转换实现

1. 下载 M3U8 文件并解析

M3U8 文件实际上包含了视频的索引信息,指示了所有的 .ts 文件(视频片段)的位置。我们可以用 m3u8 库来解析 M3U8 文件,获取其中的视频片段 URL。

import m3u8
import os
import requests

def download_m3u8(m3u8_url, download_folder):
    """
    下载并解析 M3U8 文件
    :param m3u8_url: M3U8 文件 URL
    :param download_folder: 下载的文件保存目录
    :return: 视频片段 URL 列表
    """
    # 解析 M3U8 文件
    playlist = m3u8.load(m3u8_url)
    ts_urls = []

    # 遍历 M3U8 中的每个片段 URL
    for segment in playlist.segments:
        ts_urls.append(segment.uri)

    # 下载视频片段
    if not os.path.exists(download_folder):
        os.makedirs(download_folder)
    
    ts_files = []
    for idx, ts_url in enumerate(ts_urls):
        ts_filename = os.path.join(download_folder, f"segment{idx + 1}.ts")
        ts_files.append(ts_filename)
        print(f"正在下载:{ts_url}")
        response = requests.get(ts_url, stream=True)
        with open(ts_filename, 'wb') as f:
            for chunk in response.iter_content(chunk_size=1024):
                if chunk:
                    f.write(chunk)
    
    print("所有视频片段下载完成!")
    return ts_files

# 示例用法
m3u8_url = "https://example.com/video.m3u8"
download_folder = "downloaded_video"
ts_files = download_m3u8(m3u8_url, download_folder)

2. 合并 TS 文件并转换为 MP4

下载所有的 .ts 文件后,我们可以使用 ffmpeg 来将这些视频片段合并成一个 MP4 文件。

import subprocess

def ts_to_mp4(ts_files, output_file):
    """
    使用 ffmpeg 将多个 TS 文件合并并转换为 MP4
    :param ts_files: TS 文件路径列表
    :param output_file: 输出的 MP4 文件路径
    """
    # 生成合并文件的文本
    merge_file = "merge_list.txt"
    with open(merge_file, "w") as f:
        for ts_file in ts_files:
            f.write(f"file '{ts_file}'\n")

    # 使用 ffmpeg 合并 TS 文件
    command = f"ffmpeg -f concat -safe 0 -i {merge_file} -c copy {output_file}"
    print(f"正在合并视频文件到 {output_file}...")
    subprocess.run(command, shell=True)
    
    # 删除临时合并文件列表
    os.remove(merge_file)
    print(f"视频已成功合并为: {output_file}")

# 示例用法
output_file = "output_video.mp4"
ts_to_mp4(ts_files, output_file)

3. 完整代码实现

将上述代码整合,得到一个完整的脚本,用于下载 M3U8 文件中的视频片段,并合并为 MP4 文件。

import m3u8
import os
import requests
import subprocess

def download_m3u8(m3u8_url, download_folder):
    playlist = m3u8.load(m3u8_url)
    ts_urls = []

    for segment in playlist.segments:
        ts_urls.append(segment.uri)

    if not os.path.exists(download_folder):
        os.makedirs(download_folder)
    
    ts_files = []
    for idx, ts_url in enumerate(ts_urls):
        ts_filename = os.path.join(download_folder, f"segment{idx + 1}.ts")
        ts_files.append(ts_filename)
        print(f"正在下载:{ts_url}")
        response = requests.get(ts_url, stream=True)
        with open(ts_filename, 'wb') as f:
            for chunk in response.iter_content(chunk_size=1024):
                if chunk:
                    f.write(chunk)
    
    print("所有视频片段下载完成!")
    return ts_files

def ts_to_mp4(ts_files, output_file):
    merge_file = "merge_list.txt"
    with open(merge_file, "w") as f:
        for ts_file in ts_files:
            f.write(f"file '{ts_file}'\n")

    command = f"ffmpeg -f concat -safe 0 -i {merge_file} -c copy {output_file}"
    print(f"正在合并视频文件到 {output_file}...")
    subprocess.run(command, shell=True)
    
    os.remove(merge_file)
    print(f"视频已成功合并为: {output_file}")

if __name__ == "__main__":
    m3u8_url = "https://example.com/video.m3u8"  # M3U8 文件 URL
    download_folder = "downloaded_video"        # 下载文件夹
    output_file = "output_video.mp4"            # 输出 MP4 文件

    ts_files = download_m3u8(m3u8_url, download_folder)
    ts_to_mp4(ts_files, output_file)

四、效果展示

  1. 输入:M3U8 文件 URL,如 https://example.com/video.m3u8
  2. 输出:一个 MP4 文件,包含合并后的完整视频。

五、注意事项

  1. M3U8 文件的格式:M3U8 文件中可以有不同的质量版本,可能需要选择合适的版本来下载。
  2. 视频大小:M3U8 通常是大视频流的分割文件,下载时需要稳定的网络连接。
  3. ffmpeg 配置:确保 ffmpeg 已正确安装并在系统环境变量中。

六、总结

通过本文的教程,你可以轻松实现一键下载 M3U8 文件中的所有视频片段,并将它们合并为一个 MP4 文件。这个工具适用于需要下载和处理 HLS 流的场景,操作简便且高效。

2024-11-26

Python-playwright:一款强大的UI自动化工具、新兴爬虫利器

随着Web应用程序的日益复杂,UI自动化测试和爬虫数据抓取变得越来越重要。Playwright是微软推出的一款自动化工具,专门用于自动化Web应用程序的浏览器交互。它不仅适用于UI自动化测试,也能够作为爬虫工具抓取动态生成的Web页面数据。

本文将详细介绍如何使用Python-playwright库进行Web自动化测试和爬虫数据抓取,包含基础的代码示例、功能解析、以及图解帮助你快速掌握Playwright的使用方法。


一、什么是Playwright?

Playwright是一个由微软开发的开源Web自动化框架,支持多浏览器的自动化操作,包括Chrome、Firefox和WebKit(Safari)。Playwright的主要特点包括:

  1. 支持多浏览器:与Selenium不同,Playwright不仅支持Chrome,还支持Firefox和WebKit。
  2. 自动化Web交互:可以模拟用户在Web页面上的操作,如点击、输入、滚动等。
  3. 适合动态网页抓取:Playwright能够很好地处理动态内容(如AJAX加载的内容),非常适合作为爬虫工具。

Playwright的Python绑定(即python-playwright)为开发者提供了Python接口来使用Playwright的功能,简化了浏览器自动化的实现。


二、安装Playwright

在Python中使用Playwright前,需要先安装Playwright及其浏览器驱动。可以使用以下命令进行安装:

pip install playwright
python -m playwright install

playwright install命令将自动下载需要的浏览器驱动。


三、Playwright基本用法

接下来,我们将介绍一些Playwright的基本用法,包括启动浏览器、打开页面、模拟用户操作以及抓取动态页面数据。

1. 启动浏览器并打开页面

在Playwright中,操作浏览器的对象是browser,打开页面后,操作页面的对象是page

示例:启动浏览器并访问一个网站

from playwright.sync_api import sync_playwright

# 启动Playwright并自动安装浏览器驱动
with sync_playwright() as p:
    # 启动浏览器
    browser = p.chromium.launch(headless=False)  # headless=False表示显示浏览器界面
    page = browser.new_page()  # 创建一个新的浏览器页面
    page.goto('https://example.com')  # 访问网页
    page.screenshot(path='example.png')  # 截图保存
    browser.close()  # 关闭浏览器

2. 模拟用户操作

Playwright允许模拟用户在Web页面上的交互操作,如点击、输入文本、选择下拉框等。

示例:模拟点击和文本输入

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto('https://example.com/login')
    
    # 模拟用户在用户名和密码框中输入内容
    page.fill('input[name="username"]', 'myusername')
    page.fill('input[name="password"]', 'mypassword')
    
    # 模拟点击登录按钮
    page.click('button[type="submit"]')
    
    # 等待页面加载
    page.wait_for_load_state('networkidle')
    
    # 截图保存
    page.screenshot(path='login_result.png')
    browser.close()

3. 获取页面数据

Playwright可以轻松地抓取页面中的静态或动态数据。通过选择器提取页面元素的内容并进行操作。

示例:获取网页标题和文本内容

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto('https://example.com')
    
    # 获取网页标题
    title = page.title()
    print(f"Page title: {title}")
    
    # 获取网页中的文本
    heading = page.text_content('h1')
    print(f"Page heading: {heading}")
    
    browser.close()

四、Playwright的高级功能

1. 等待元素加载

在Web自动化中,经常需要等待某些元素加载完毕才能进行下一步操作。Playwright提供了灵活的等待机制。

示例:等待元素出现

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto('https://example.com')
    
    # 等待特定元素加载完成
    page.wait_for_selector('h1')
    
    # 获取元素文本
    heading = page.text_content('h1')
    print(f"Page heading: {heading}")
    
    browser.close()

2. 截图和视频录制

Playwright支持截取页面截图和录制浏览器会话,方便进行自动化测试或生成调试信息。

示例:录制浏览器会话

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page(record_video_dir='./videos')  # 设置视频录制目录
    page.goto('https://example.com')
    
    # 进行一些操作
    page.click('button')
    
    # 录制视频
    page.close()
    browser.close()

3. 处理弹窗和对话框

Playwright可以处理Web应用中的弹窗、对话框等用户交互元素。

示例:自动接受对话框

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto('https://example.com/alert')
    
    # 监听并自动接受弹窗
    page.on('dialog', lambda dialog: dialog.accept())
    
    # 触发弹窗
    page.click('button')
    
    browser.close()

五、Playwright在爬虫中的应用

Playwright不仅是自动化测试的利器,也是一个非常强大的爬虫工具。它能够处理JavaScript渲染的动态内容,解决传统爬虫工具(如requests和BeautifulSoup)无法处理的动态网页问题。

示例:使用Playwright抓取动态加载的数据

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto('https://quotes.toscrape.com/js/')
    
    # 等待数据加载完成
    page.wait_for_selector('.quote')
    
    # 获取所有的引用文本
    quotes = page.query_selector_all('.quote span.text')
    for quote in quotes:
        print(quote.text_content())
    
    browser.close()

六、总结

Playwright是一个强大的Web自动化框架,适用于UI自动化测试和动态网页抓取。它支持多浏览器(Chrome、Firefox和WebKit),能够轻松模拟用户交互操作,并且在抓取动态网页时比传统的爬虫工具更为高效。

在本文中,我们:

  • 介绍了Playwright的安装与基础使用
  • 演示了如何模拟浏览器操作、获取网页数据
  • 展示了Playwright的高级功能,如等待元素加载、处理弹窗和录制视频
  • 讲解了如何使用Playwright进行动态网页的抓取

无论是进行Web自动化测试,还是抓取动态数据,Playwright都提供了一个简洁、高效的解决方案,值得每个开发者学习和掌握。

2024-11-26

Python 中 bs4soup.find()soup.find_all() 用法

在网页抓取与解析中,BeautifulSoup(通常简称为 bs4)是一个非常流行的 Python 库,用于解析 HTML 或 XML 文档。它提供了简便的 API,使得从网页中提取特定信息变得更加高效和直观。find()find_all()BeautifulSoup 中两个最常用的方法,它们允许我们根据标签名称、属性等条件来查找和提取网页内容。

本文将详细讲解 find()find_all() 方法的用法,包括它们的参数、返回值、区别,以及如何通过代码示例来理解它们的应用。


一、BeautifulSoup 简介

BeautifulSoup 是一个用于从 HTML 和 XML 文档中提取数据的 Python 库。它提供了多种方法来遍历文档树、查找特定的标签、提取标签内容等。

安装 BeautifulSoup

首先,我们需要安装 beautifulsoup4requests 库(用于发送 HTTP 请求)。可以通过以下命令安装:

pip install beautifulsoup4 requests

二、soup.find() 方法

1. 方法定义

find() 方法用于查找匹配的第一个标签。它根据传入的标签名称、属性、文本内容等查找符合条件的第一个标签。如果没有找到匹配的标签,返回 None

soup.find(name, attrs, recursive, string, limit, **kwargs)
  • name:标签名称(如 adiv)。
  • attrs:标签的属性(如 classid)。
  • recursive:布尔值,指定是否递归查找子标签。
  • string:标签内的文本内容。
  • limit:返回的结果数量,默认为 None(即返回第一个匹配的标签)。
  • **kwargs:用于传入其他标签属性。

2. 示例:查找第一个 <a> 标签

假设我们有一个简单的 HTML 文档如下:

<html>
    <body>
        <h1>Python Web Scraping</h1>
        <a href="https://example.com">Example 1</a>
        <a href="https://python.org">Example 2</a>
    </body>
</html>

以下是如何使用 find() 方法查找第一个 <a> 标签:

from bs4 import BeautifulSoup

# 示例 HTML 内容
html_content = """
<html>
    <body>
        <h1>Python Web Scraping</h1>
        <a href="https://example.com">Example 1</a>
        <a href="https://python.org">Example 2</a>
    </body>
</html>
"""

# 解析 HTML
soup = BeautifulSoup(html_content, 'html.parser')

# 查找第一个 <a> 标签
first_a_tag = soup.find('a')

# 输出结果
print(first_a_tag)

输出:

<a href="https://example.com">Example 1</a>

说明:

  • soup.find('a') 返回第一个 <a> 标签,包含 href 属性和文本内容 "Example 1"。
  • find() 方法只返回第一个匹配的标签。如果有多个 <a> 标签,它不会返回其他标签。

3. 使用属性查找标签

find() 方法不仅可以通过标签名称查找,还可以通过标签的属性来查找。例如,通过 idclass 属性查找。

示例:通过 class 查找标签

<html>
    <body>
        <h1>Python Web Scraping</h1>
        <div class="content">This is content 1</div>
        <div class="content">This is content 2</div>
    </body>
</html>
# 查找第一个 class 为 'content' 的 div 标签
content_div = soup.find('div', class_='content')

# 输出结果
print(content_div)

输出:

<div class="content">This is content 1</div>

说明:

  • 通过 class_='content' 查找第一个 class 属性为 "content" 的 div 标签。
  • class_find() 方法的一个关键字参数,用于匹配标签的 class 属性(注意:这里的 class 是 Python 保留字,因此使用 class_)。

三、soup.find_all() 方法

1. 方法定义

find_all() 方法用于查找所有匹配的标签,返回一个列表。如果没有找到匹配的标签,返回一个空列表。

soup.find_all(name, attrs, recursive, string, limit, **kwargs)
  • name:标签名称。
  • attrs:标签的属性。
  • recursive:布尔值,控制是否递归查找子标签。
  • string:标签内的文本内容。
  • limit:返回结果的数量,默认返回所有匹配标签。
  • **kwargs:用于传入其他标签属性。

2. 示例:查找所有 <a> 标签

假设我们有多个 <a> 标签的 HTML 文档:

<html>
    <body>
        <h1>Python Web Scraping</h1>
        <a href="https://example.com">Example 1</a>
        <a href="https://python.org">Example 2</a>
        <a href="https://github.com">Example 3</a>
    </body>
</html>

以下是如何使用 find_all() 方法查找所有 <a> 标签:

# 查找所有 <a> 标签
a_tags = soup.find_all('a')

# 输出结果
for a in a_tags:
    print(a)

输出:

<a href="https://example.com">Example 1</a>
<a href="https://python.org">Example 2</a>
<a href="https://github.com">Example 3</a>

说明:

  • soup.find_all('a') 返回所有 <a> 标签,输出的是一个列表。
  • find_all() 方法返回所有匹配的标签,可以通过循环遍历它们。

3. 限制返回结果数量

你可以使用 limit 参数限制返回结果的数量。比如,只返回前两个 <a> 标签。

示例:限制返回前两个 <a> 标签

# 查找前两个 <a> 标签
a_tags_limit = soup.find_all('a', limit=2)

# 输出结果
for a in a_tags_limit:
    print(a)

输出:

<a href="https://example.com">Example 1</a>
<a href="https://python.org">Example 2</a>

四、find()find_all() 的区别

  • find() 只返回第一个匹配的标签。
  • find_all() 返回所有匹配的标签,通常是一个列表,即使只有一个标签满足条件,返回的也是列表。
方法返回值使用场景
find()单个标签或 None仅需第一个匹配的标签
find_all()列表(可能为空)需要多个标签时使用

五、总结

  • find() 方法:用于查找第一个匹配的标签。适用于只关心第一个符合条件的标签的情况。
  • find_all() 方法:用于查找所有匹配的标签,返回一个列表,适用于需要获取多个标签的情况。
  • 通过标签名称、属性、文本等 可以进行条件筛选,使用灵活方便。

通过本文的讲解,你应该已经掌握了 BeautifulSoupfind()find_all() 方法的用法,能够在实际项目中灵活应用这两个方法进行网页数据抓取和解析。

2024-11-26

Python 学习之 requests 库的基本使用

requests 是一个功能强大且简洁的 Python 库,主要用于发送 HTTP 请求。它支持多种 HTTP 方法(如 GET、POST、PUT、DELETE 等),并提供了简单易用的接口来处理请求和响应,广泛应用于 Web 数据抓取、API 调用、自动化测试等领域。

本文将详细介绍 requests 库的基本使用方法,通过代码示例和图解帮助你更好地理解和掌握该库。


一、安装 requests

在开始使用 requests 库之前,首先需要安装它。可以使用 pip 安装:

pip install requests

安装完成后,你就可以在 Python 中导入并使用该库了。


二、发送 HTTP 请求

requests 库支持多种 HTTP 请求方法,包括 GETPOSTPUTDELETE 等。我们首先来看一下最常用的 GETPOST 请求的使用方法。

1. GET 请求

GET 请求通常用于从服务器获取数据。我们可以通过 requests.get() 方法发送一个 GET 请求,并获取服务器的响应。

示例:发送 GET 请求

import requests

# 发送 GET 请求
response = requests.get('https://jsonplaceholder.typicode.com/posts/1')

# 输出响应状态码
print(f"Status Code: {response.status_code}")

# 输出响应内容
print(f"Response Text: {response.text}")

# 输出响应的 JSON 数据
print(f"JSON Data: {response.json()}")

说明:

  • requests.get():发送 GET 请求。
  • response.status_code:获取响应的状态码(例如 200 表示请求成功)。
  • response.text:获取响应的文本内容。
  • response.json():如果响应数据为 JSON 格式,可以使用 .json() 方法将其转换为 Python 字典。

2. POST 请求

POST 请求通常用于向服务器提交数据。例如,提交表单数据或上传文件。我们可以通过 requests.post() 方法发送一个 POST 请求。

示例:发送 POST 请求

import requests

# 发送 POST 请求,传递表单数据
data = {
    'title': 'foo',
    'body': 'bar',
    'userId': 1
}

response = requests.post('https://jsonplaceholder.typicode.com/posts', data=data)

# 输出响应状态码
print(f"Status Code: {response.status_code}")

# 输出响应的 JSON 数据
print(f"Response JSON: {response.json()}")

说明:

  • requests.post():发送 POST 请求。
  • data:可以通过 data 参数发送表单数据(字典形式)。
  • response.json():获取响应的 JSON 数据。

三、传递参数

在发送请求时,常常需要携带一些查询参数(如 GET 请求的查询字符串)或表单数据(如 POST 请求)。requests 库提供了方便的方法来处理这些参数。

1. GET 请求中的查询参数

GET 请求中,可以通过 params 参数来传递查询字符串。

示例:传递查询参数

import requests

# 发送 GET 请求,传递查询参数
params = {
    'userId': 1
}

response = requests.get('https://jsonplaceholder.typicode.com/posts', params=params)

# 输出响应的 JSON 数据
print(response.json())

说明:

  • params:将查询参数以字典的形式传递,requests 会自动将其转化为查询字符串并附加到 URL 后面。

2. POST 请求中的表单数据

POST 请求中的表单数据可以通过 data 参数传递。

示例:传递表单数据

import requests

# 发送 POST 请求,传递表单数据
data = {
    'username': 'john',
    'password': '1234'
}

response = requests.post('https://httpbin.org/post', data=data)

# 输出响应的 JSON 数据
print(response.json())

说明:

  • data:以字典的形式传递表单数据,requests 会将其编码为 application/x-www-form-urlencoded 格式。

四、处理请求头

有时我们需要在请求中设置自定义请求头(如 User-AgentAuthorization 等)。可以通过 headers 参数来传递请求头。

示例:设置请求头

import requests

# 设置自定义请求头
headers = {
    'User-Agent': 'my-app',
    'Authorization': 'Bearer <your_token>'
}

response = requests.get('https://jsonplaceholder.typicode.com/posts', headers=headers)

# 输出响应状态码
print(response.status_code)

说明:

  • headers:将请求头信息以字典形式传递给 requests.get()requests.post() 方法。

五、处理响应

HTTP 响应包括状态码、响应体、响应头等信息。requests 库提供了多种方法来访问这些信息。

1. 获取状态码

可以使用 response.status_code 获取 HTTP 响应的状态码。

response = requests.get('https://jsonplaceholder.typicode.com/posts')
print(f"Status Code: {response.status_code}")

2. 获取响应体

可以通过 response.text 获取响应的内容,返回的是字符串类型。

print(f"Response Text: {response.text}")

3. 获取 JSON 数据

如果响应内容是 JSON 格式,可以通过 response.json() 将其解析为 Python 字典。

data = response.json()
print(f"Response JSON: {data}")

4. 获取响应头

可以通过 response.headers 获取响应头,返回的是一个字典。

print(f"Response Headers: {response.headers}")

六、常见问题

1. 设置请求超时

为了避免请求卡住太长时间,可以设置请求超时时间。通过 timeout 参数来设置。

示例:设置请求超时

import requests

try:
    response = requests.get('https://jsonplaceholder.typicode.com/posts', timeout=3)
    print(response.text)
except requests.exceptions.Timeout:
    print("The request timed out.")

说明:

  • timeout:设置请求的最大等待时间(秒)。如果请求超过该时间,将引发 Timeout 异常。

2. 处理异常

requests 库在发送请求时可能会遇到各种网络异常,如连接错误、超时错误等。我们可以使用 try-except 来捕获这些异常。

示例:处理异常

import requests

try:
    response = requests.get('https://jsonplaceholder.typicode.com/posts')
    response.raise_for_status()  # 如果响应状态码不是 200,会抛出 HTTPError 异常
except requests.exceptions.HTTPError as err:
    print(f"HTTP Error: {err}")
except requests.exceptions.RequestException as err:
    print(f"Error: {err}")

说明:

  • response.raise_for_status():如果响应状态码不是 2xx,将抛出 HTTPError 异常。

七、总结

requests 是一个非常简洁且功能强大的 Python 库,用于发送 HTTP 请求和处理响应。本文详细介绍了 GETPOST 请求的基本用法,并展示了如何传递参数、设置请求头、处理响应和常见的异常情况。

掌握了 requests 库后,你就可以轻松地进行 Web 数据抓取、调用 API、自动化测试等工作。希望通过本文的学习,你能更好地理解和使用 requests 库。

2024-11-26

超实用的 Python 库之 lxml 使用详解

lxml 是一个功能强大的 Python 库,用于处理 XML 和 HTML 文档,支持高效的文档解析、树形结构操作以及 XPath 和 XSLT 功能。它不仅速度快,而且功能丰富,广泛应用于数据提取和网页爬虫等领域。

本文将详细介绍 lxml 的使用方法,包括代码示例和图解,帮助你轻松掌握这一工具。


一、安装 lxml

在使用 lxml 前,请确保已安装该库。可以通过以下命令安装:

pip install lxml

二、基本功能概览

lxml 提供以下核心功能:

  1. 解析 XML/HTML:快速读取并处理文档。
  2. 树形结构操作:轻松增删改查节点。
  3. XPath 支持:通过强大的查询语言快速定位节点。
  4. 高效处理大文档:在内存友好的方式下解析大文件。

三、lxml 的主要模块

  • lxml.etree:操作 XML 和 HTML 的主要模块。
  • lxml.html:专门处理 HTML 文档。

四、XML 文档解析与操作

1. 加载和解析 XML

lxml.etree 支持从字符串或文件中解析 XML。

示例代码

from lxml import etree

# 从字符串加载 XML
xml_data = """<root>
    <item id="1">Item 1</item>
    <item id="2">Item 2</item>
</root>"""
tree = etree.XML(xml_data)

# 输出 XML 格式
print(etree.tostring(tree, pretty_print=True).decode())

输出

<root>
  <item id="1">Item 1</item>
  <item id="2">Item 2</item>
</root>

2. XPath 查询

XPath 是一种用于导航 XML 树形结构的语言。

示例代码

# 获取所有 <item> 节点
items = tree.xpath("//item")
for item in items:
    print(item.text)

# 获取 id="1" 的节点
item_1 = tree.xpath("//item[@id='1']")[0]
print(f"节点内容: {item_1.text}")

输出

Item 1
Item 2
节点内容: Item 1

3. 节点操作

lxml 提供了强大的节点操作功能。

示例代码

# 修改节点文本
item_1.text = "Updated Item 1"

# 添加新节点
new_item = etree.Element("item", id="3")
new_item.text = "Item 3"
tree.append(new_item)

# 删除节点
tree.remove(item_1)

# 输出更新后的 XML
print(etree.tostring(tree, pretty_print=True).decode())

输出

<root>
  <item id="2">Item 2</item>
  <item id="3">Item 3</item>
</root>

五、HTML 文档解析与操作

lxml.html 是处理 HTML 的专用模块,尤其适合网页爬取。

1. 加载和解析 HTML

示例代码

from lxml import html

# 加载 HTML 字符串
html_data = """<html>
    <body>
        <h1>Title</h1>
        <p class="content">This is a paragraph.</p>
    </body>
</html>"""
tree = html.fromstring(html_data)

# 输出格式化 HTML
print(html.tostring(tree, pretty_print=True).decode())

输出

<html>
  <body>
    <h1>Title</h1>
    <p class="content">This is a paragraph.</p>
  </body>
</html>

2. 提取内容

lxml.html 支持快速提取 HTML 元素内容。

示例代码

# 获取标题文本
title = tree.xpath("//h1/text()")[0]
print(f"标题: {title}")

# 获取段落文本
paragraph = tree.xpath("//p[@class='content']/text()")[0]
print(f"段落: {paragraph}")

输出

标题: Title
段落: This is a paragraph.

3. 修改和生成 HTML

可以动态操作 HTML 节点。

示例代码

# 修改标题文本
tree.xpath("//h1")[0].text = "Updated Title"

# 添加新段落
new_paragraph = etree.Element("p", class_="content")
new_paragraph.text = "Another paragraph."
tree.body.append(new_paragraph)

# 输出更新后的 HTML
print(html.tostring(tree, pretty_print=True).decode())

输出

<html>
  <body>
    <h1>Updated Title</h1>
    <p class="content">This is a paragraph.</p>
    <p class="content">Another paragraph.</p>
  </body>
</html>

六、性能优化:处理大文件

对于大型 XML 文件,使用逐步解析的方式节省内存。

示例代码

from lxml import etree

# 使用迭代解析器
context = etree.iterparse("large.xml", events=("start", "end"))

for event, elem in context:
    if event == "end" and elem.tag == "item":
        print(elem.text)
        elem.clear()  # 释放内存

七、与 BeautifulSoup 的对比

功能lxmlBeautifulSoup
性能更快,适合大文件较慢,适合小文件
功能丰富度支持 XPath 和 XSLT仅支持 CSS Selector
学习曲线适中,需了解树形结构和 XPath简单,上手快

八、常见问题及解决方法

1. 为什么 lxml 的 XPath 查询返回空?

确保使用正确的语法:

  • 对于 HTML,/html/body 开始查询。
  • 对于 XML,/root 开始查询。

2. 如何解析非标准 HTML?

使用 html 模块的容错机制:

tree = html.fromstring("<div><p>Missing end tag")

九、总结

lxml 是一个强大的库,适合处理 XML 和 HTML 数据,具有以下优势:

  1. 支持高效的文档解析和操作。
  2. 提供强大的 XPath 查询和树形结构操作。
  3. 性能优异,能够处理大文档。

通过学习本文内容,你可以轻松上手 lxml,并在数据爬取和 XML/HTML 操作中大显身手!

2024-08-27



import requests
 
# 定义一个获取代理IP的函数
def get_proxy():
    # 这里应该是获取代理IP的逻辑,例如从代理服务提供商获取或者从本地代理池中获取
    # 这里仅作为示例,使用静态配置的代理IP
    return {
        'http': 'http://123.123.123.123:8080',
        'https': 'https://123.123.123.123:8080'
    }
 
# 定义一个使用代理的请求函数
def request_with_proxy(url, proxy=None):
    try:
        response = requests.get(url, proxies=proxy)
        if response.status_code == 200:
            return response.text
        else:
            print(f"请求失败,状态码: {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"请求异常: {e}")
 
# 定义一个翻译的函数
def translate(query, proxy):
    url = f"https://fanyi.baidu.com/sug"
    data = {
        'kw': query
    }
    response = request_with_proxy(url, proxy)
    if response:
        print(response)  # 输出翻译结果,实际应用中可以进行进一步的解析和处理
    else:
        print("翻译失败")
 
# 使用代理进行请求
proxy = get_proxy()
query = "crawl"  # 这里是待翻译的单词
translate(query, proxy)

这个示例展示了如何使用代理IP进行网络请求。在实际应用中,你需要替换get_proxy函数中的代理IP地址为有效的代理服务地址和端口。同时,translate函数中的URL和POST数据应该根据实际的百度翻译API进行调整。这只是一个简单的示例,实际的爬虫项目可能需要更复杂的处理,例如错误处理、代理IP的有效性检查、自动更换代理、登录处理等。

2024-08-27

由于这个问题涉及的内容较多,我将提供一个简化版的核心代码实例,展示如何使用Python进行电力能耗数据的爬取和基本分析。




from pyspark.sql import SparkSession
from pyspark.sql.functions import *
import pyspark.sql.functions as F
 
# 初始化Spark会话
spark = SparkSession.builder.appName("EnergyConsumptionAnalysis").getOrCreate()
 
# 假设电力能耗数据已经通过爬虫技术爬取并保存到了CSV文件中
energyDataCSVPath = "path/to/energy_consumption_data.csv"
 
# 读取CSV文件到DataFrame
energyDataDF = spark.read.csv(energyDataCSVPath, header=True, inferSchema=True)
 
# 重命名列,以符合你的模型或分析需要
energyDataDF = energyDataDF.withColumnRenamed("date", "date") \
                           .withColumnRenamed("value", "energy_consumed")
 
# 转换日期格式,如果需要
energyDataDF = energyDataDF.withColumn("date", to_date(col("date"), "yyyy-MM-dd"))
 
# 按日期分组,并计算每日能耗总和
dailyEnergyConsumption = energyDataDF.groupBy("date").agg(sum("energy_consumed").alias("total_consumed"))
 
# 将结果显示为DataFrame
dailyEnergyConsumption.show()
 
# 关闭Spark会话
spark.stop()

这个代码实例展示了如何使用PySpark读取CSV文件,进行数据的简单处理(例如重命名列和转换日期格式),并计算每日的能耗总和。在实际应用中,你需要根据你的具体需求来调整这个代码,例如添加数据清洗步骤、更复杂的聚合操作或者可视化代码。