Python爬虫核心技巧:两步法高效提取HTML数据

本文解决爬虫新手最常遇到的两个问题:

  1. 面对几千个div标签,如何精准定位目标数据?
  2. 为什么标题和内容总是对应不上?

一、问题场景

假设你要爬取招聘网站的详情页,页面有以下信息:

  • 单位简介
  • 招聘简章
  • 招聘职位
  • 宣讲会信息

每部分都有标题和内容,你需要把它们全部提取出来。

难点

  • 页面HTML有10万多字符
  • 有3000多个`
    `标签
  • 如何快速定位?如何保证标题和内容对应?

二、传统做法的问题

❌ 错误方法1:遍历所有div

all_divs = soup.find_all('div')  # 找到3000多个div
for div in all_divs:
    if '单位简介' in div.get_text():
        1. 找到了...但其他呢?

问题

  • 效率低下,需要遍历3000多个元素
  • 逻辑复杂,难以维护

❌ 错误方法2:分别查找标题和内容

1. 找所有标题
titles = soup.find_all('div', class_='dm-tit')
1. 结果:["单位简介", "招聘简章", "招聘职位"]

1. 找所有内容
contents = soup.find_all('div', class_='dm-cont')
1. 结果:["深圳能源集团...", "2024年招聘...", "软件工程师..."]

1. 尝试对应
for i, title in enumerate(titles):
    print(f"{title}: {contents[i]}")

问题

  • 看起来能工作,但非常脆弱
  • 如果网站插入其他元素,顺序就乱了
  • 无法保证对应关系

三、正确方法:两步法

核心思想

第一步:初步定位 - 找到数据容器(外层盒子)
第二步:细节提取 - 在容器内提取标题和内容

3.1 第一步:初步定位

用浏览器找规律

  1. 打开网页,右键点击”单位简介” → 选择”检查”
  2. 观察HTML结构:
<div class="detail-module">
  <div class="dm-tit">单位简介</div>
  <div class="dm-cont">深圳能源集团股份有限公司...</div>
</div>

<div class="detail-module">
  <div class="dm-tit">招聘简章</div>
  <div class="dm-cont">2024年招聘...</div>
</div>

<div class="detail-module">
  <div class="dm-tit">招聘职位</div>
  <div class="dm-cont">软件工程师...</div>
</div>

发现规律

关键发现

  • 所有数据都在 class="detail-module" 的容器里
  • 每个容器包含一个标题(dm-tit)和一个内容(dm-cont)
  • 这是一个重复的结构

用代码定位

import requests
from bs4 import BeautifulSoup

1. 获取页面
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')

1. 第一步:找到所有容器
modules = soup.find_all('div', class_='detail-module')
print(f"找到 {len(modules)} 个模块")  # 输出:找到 4 个模块

这一步的本质

  • 从3000个div中,精准定位到4个目标容器
  • 大幅缩小搜索范围

3.2 第二步:细节提取

在容器内提取数据

data = {}

for module in modules:  # 遍历每个模块
    1. 在当前模块内找标题
    title = module.find('div', class_='dm-tit')

    1. 在当前模块内找内容
    content = module.find('div', class_='dm-cont')

    1. 提取文本
    if title and content:
        title_text = title.get_text(strip=True)
        content_text = content.get_text(strip=True)

        1. 保存数据
        data[title_text] = content_text

print(data)

输出

{
  "单位简介": "深圳能源集团股份有限公司成立于1991年...",
  "招聘简章": "2024年校园招聘计划...",
  "招聘职位": "软件工程师、产品经理...",
  "宣讲会信息": "宣讲时间:2025年10月11日..."
}

四、关键理解:为什么要在容器内查找?

对比两种方法

❌ 直接查找(不推荐)

titles = soup.find_all('div', class_='dm-tit')
contents = soup.find_all('div', class_='dm-cont')

1. 假设网站结构变化,插入了其他元素:
<div class="detail-module">
  <div class="dm-tit">单位简介</div>
  <div class="dm-cont">深圳能源集团...</div>
</div>

<div class="other-section">
  <div class="dm-tit">注意事项</div>  <!-- 插入的无关内容 -->
</div>

<div class="detail-module">
  <div class="dm-tit">招聘简章</div>
  <div class="dm-cont">2024年招聘...</div>
</div>
titles = ["单位简介", "注意事项", "招聘简章"]
contents = ["深圳能源集团...", "2024年招聘..."]

1. 对应关系乱了!
1. titles[1] = "注意事项"
1. contents[1] = "2024年招聘..." ← 错误!

✅ 容器内查找(推荐)

modules = soup.find_all('div', class_='detail-module')  # 只找目标容器

for module in modules:
    title = module.find('div', class_='dm-tit')      # 在容器内找
    content = module.find('div', class_='dm-cont')   # 在容器内找
    1. title 和 content 永远来自同一个 module
    1. 对应关系100%正确!

无论插入多少其他元素,都不受影响!


五、完整代码示例

5.1 提取招聘详情页数据

import requests
from bs4 import BeautifulSoup

def extract_career_detail(career_id):
    """提取招聘详情页数据"""
    url = f"https://csust.bysjy.com.cn/detail/career?id={career_id}"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }

    1. 获取页面
    response = requests.get(url, headers=headers, timeout=10)
    soup = BeautifulSoup(response.text, 'html.parser')

    detail = {}

    1. ==========================================
    1. 第一步:初步定位 - 找到所有模块容器
    1. ==========================================
    modules = soup.find_all('div', class_='detail-module')
    print(f"✓ 找到 {len(modules)} 个模块")

    1. ==========================================
    1. 第二步:细节提取 - 遍历每个模块
    1. ==========================================
    for module in modules:
        1. 在模块内找标题
        title_elem = module.find('div', class_='dm-tit')

        1. 在模块内找内容
        content_elem = module.find('div', class_='dm-cont')

        if title_elem and content_elem:
            title = title_elem.get_text(strip=True)
            content = content_elem.get_text(strip=True)
            detail[title] = content
            print(f"  - 提取:{title} ({len(content)} 字符)")

    return detail

1. 测试
data = extract_career_detail('668868')
print(f"\n提取到 {len(data)} 个字段")

输出

✓ 找到 4 个模块
  - 提取:宣讲会信息 (149 字符)
  - 提取:单位简介 (397 字符)
  - 提取:招聘简章 (822 字符)
  - 提取:招聘职位 (8471 字符)

提取到 4 个字段

5.2 批量提取多个页面

def batch_extract(career_ids):
    """批量提取多个页面"""
    results = []

    for i, career_id in enumerate(career_ids):
        print(f"\n[{i+1}/{len(career_ids)}] 正在提取 ID={career_id}")

        try:
            detail = extract_career_detail(career_id)
            results.append({
                'career_id': career_id,
                **detail  # 展开详情数据
            })
        except Exception as e:
            print(f"  ✗ 提取失败: {e}")

        1. 礼貌延迟,避免被封IP
        import time
        time.sleep(0.5)

    return results

1. 测试
ids = ['667200', '667839', '668868']
data = batch_extract(ids)

1. 保存为JSON
import json
with open('careers.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

print(f"\n✓ 成功提取 {len(data)} 条数据")

六、核心概念速查

6.1 三个核心函数

函数 作用 返回值
find_all() 找所有符合条件的元素 列表 [elem1, elem2, ...]
find() 找第一个符合条件的元素 单个元素 `
`
get_text(strip=True) 提取文本并去除空格 字符串 "深圳能源集团..."

6.2 查找语法

1. 按class查找
soup.find('div', class_='detail-module')
soup.find_all('div', class_='dm-tit')

1. 按id查找
soup.find('div', id='header')

1. CSS选择器(更强大)
soup.select_one('div.detail-module')  # 等同于 find()
soup.select('div.dm-tit')             # 等同于 find_all()

1. 嵌套查找
container = soup.find('div', class_='container')
title = container.find('h2', class_='title')  # 在容器内查找

6.3 提取数据

1. 提取文本
elem.get_text()              # "  深圳能源  \n"
elem.get_text(strip=True)    # "深圳能源" (推荐)

1. 提取属性
link = soup.find('a', class_='job-link')
href = link.get('href')      # 获取链接
title = link.get('title')    # 获取title属性

1. 判断元素是否存在
elem = soup.find('div', class_='optional')
if elem:
    text = elem.get_text(strip=True)
else:
    text = "未找到"

七、两步法流程图

┌─────────────────────────────────────────────────────────┐
│                    第一步:初步定位                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  soup.find_all('div', class_='detail-module')          │
│                          ↓                              │
│         [模块1, 模块2, 模块3, 模块4]                    │
│                                                         │
└─────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────┐
│                    第二步:细节提取                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  for module in modules:                                 │
│      ├─ title = module.find('div', class_='dm-tit')    │
│      ├─ content = module.find('div', class_='dm-cont') │
│      └─ data[title.get_text()] = content.get_text()    │
│                                                         │
└─────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────┐
│                       提取结果                           │
├─────────────────────────────────────────────────────────┤
│  {                                                      │
│    "单位简介": "深圳能源集团股份有限公司...",            │
│    "招聘简章": "2024年招聘...",                         │
│    "招聘职位": "软件工程师...",                         │
│    "宣讲会信息": "宣讲时间:2025年10月11日..."          │
│  }                                                      │
└─────────────────────────────────────────────────────────┘

八、实战技巧

8.1 如何快速找到class名称?

使用浏览器开发者工具

  1. F12 打开开发者工具
  2. 点击左上角的”选择元素”图标(或按 Ctrl+Shift+C
  3. 鼠标悬停在网页内容上,会高亮对应的HTML
  4. 点击,查看右侧的HTML代码
  5. 找到 class="xxx" 就是你要用的class名称

8.2 处理可选字段

有些页面可能缺少某些字段,需要判断:

for module in modules:
    title_elem = module.find('div', class_='dm-tit')
    content_elem = module.find('div', class_='dm-cont')

    1. 判断是否存在
    if title_elem and content_elem:
        title = title_elem.get_text(strip=True)
        content = content_elem.get_text(strip=True)
        detail[title] = content
    else:
        print("  ⚠ 模块缺少标题或内容,跳过")

8.3 处理多种class名称

有些网站使用不同的class名称:

1. 尝试多个可能的class
title = (module.find('div', class_='dm-tit') or
         module.find('div', class_='title') or
         module.find('h2', class_='section-title'))

if title:
    title_text = title.get_text(strip=True)

8.4 提取嵌套内容

1. 有些内容有多层嵌套
content_elem = module.find('div', class_='dm-cont')

1. 提取所有段落
paragraphs = content_elem.find_all('p')
full_text = '\n'.join(p.get_text(strip=True) for p in paragraphs)

1. 提取列表
items = content_elem.find_all('li')
item_list = [item.get_text(strip=True) for item in items]

九、常见问题

Q1: 为什么find_all()返回空列表?

可能原因

  1. class名称写错了(区分大小写)
  2. 元素是动态加载的(需要用Playwright等工具)
  3. 页面还没加载完

解决方法

modules = soup.find_all('div', class_='detail-module')
if not modules:
    print("未找到模块,检查class名称是否正确")
    print("页面前1000字符:", response.text[:1000])

Q2: 提取的文本有很多空格和换行?

解决:使用 strip=True

1. ❌ 不好
text = elem.get_text()  # "  深圳能源  \n  集团  \n"

1. ✅ 好
text = elem.get_text(strip=True)  # "深圳能源集团"

1. ✅ 更好(替换多余空白)
import re
text = elem.get_text(strip=True)
text = re.sub(r'\s+', ' ', text)  # 把多个空格/换行替换成单个空格

Q3: 如何处理表格数据?

table = soup.find('table', class_='job-table')
rows = table.find_all('tr')

data = []
for row in rows:
    cells = row.find_all(['td', 'th'])
    row_data = [cell.get_text(strip=True) for cell in cells]
    data.append(row_data)

1. 结果
1. [
1.   ["职位", "薪资", "学历"],
1.   ["软件工程师", "20K-30K", "本科"],
1.   ["产品经理", "25K-35K", "本科"]
1. ]

十、总结

核心要点

  1. 两步法

    • 第一步:初步定位容器(find_all
    • 第二步:在容器内细节提取(find
  2. 为什么要用容器

    • 缩小搜索范围
    • 保证数据对应关系
    • 提高代码健壮性
  3. 关键函数

    • find_all() – 找所有
    • find() – 找第一个
    • get_text(strip=True) – 提取文本

最佳实践

1. ✅ 推荐的完整流程
def extract_data(url):
    1. 1. 获取页面
    response = requests.get(url, headers=headers)
    soup = BeautifulSoup(response.text, 'html.parser')

    1. 2. 初步定位:找容器
    containers = soup.find_all('div', class_='container')

    1. 3. 细节提取:在容器内查找
    results = []
    for container in containers:
        item = {}

        1. 在容器内查找各个字段
        title = container.find('h2', class_='title')
        content = container.find('div', class_='content')
        date = container.find('span', class_='date')

        1. 提取文本(判断是否存在)
        if title:
            item['title'] = title.get_text(strip=True)
        if content:
            item['content'] = content.get_text(strip=True)
        if date:
            item['date'] = date.get_text(strip=True)

        results.append(item)

    return results

附录:完整项目代码

完整的两阶段爬虫代码(获取列表 + 提取详情)可在我的GitHub仓库查看:

核心代码

  • crawl_details_without_api.py – 完整爬虫
  • 教程文件/ – 详细教程和示例

希望这篇文章能帮助你掌握高效的HTML数据提取方法!如果有任何问题,欢迎在评论区留言。

发表回复

Your email address will not be published. Required fields are marked *.

*
*