爬虫阶段三实战练习题二:使用 Selenium 模拟爬取拉勾网职位表

爬虫阶段三实战练习题二:使用 Selenium 模拟爬取拉勾网职位表

目标

  • 爬取拉勾网 Python 职位信息(职位名称、公司、薪资、地点、经验要求)
  • 要求:先尝试分析 Ajax 接口,若失败则用 Selenium 模拟滚动加载

分析 Ajax 接口(首选)

  1. 打开拉勾网 Python 职位页面:https://www.lagou.com/zhaopin/Python/
  2. 打开开发者工具 Network → XHR,刷新页面,并向下滚动加载更多。
  3. 观察请求,可能会发现 https://www.lagou.com/jobs/positionAjax.json 或类似的接口。
  4. 查看请求参数,可能有 cityneedAddtionalResultisSchoolJobpageNopageSize 等。
  5. 拉勾网的反爬较强,需要设置请求头,尤其是 RefererCookie,且参数可能包含加密的 signtoken
  6. 如果参数简单且没有加密,可以直接模拟。

结果展示

调试好的代码

python 复制代码
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from selenium.webdriver import ActionChains
import time
import json
import re

# 指定刚才解压的 chromedriver.exe 的完整路径
path = r'D:\Program Files (x86)\chromedriver\chromedriver-win64\chromedriver.exe'  # 请根据你的实际存放位置修改
service = Service(executable_path=path)



# 配置浏览器选项,尝试绕过检测
options = webdriver.ChromeOptions()
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)

# 启动浏览器
driver = webdriver.Chrome(options=options)
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")

# 打开拉勾网 Python 职位页
driver.get('https://www.lagou.com/zhaopin/Python/')



# --- 第一步:定位滑块元素 ---
# 注意:滑块的元素可能会变化,常见的有以下几种
# 等待滑块按钮出现(通常滑块是动态加载的)
slider = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.CLASS_NAME, "btn_slide"))
)

action = ActionChains(driver)
action.click_and_hold(slider).perform()
time.sleep(0.5)

# 尝试向右拖动 300 像素(可以适当加大,比如 400)
action.move_by_offset(300, 0).perform()
time.sleep(0.5)

action.release().perform()

# 等待职位列表加载
wait = WebDriverWait(driver, 2000)



print("等到之前")
wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'item_con_list')))
print("等到之后")


# 模拟滚动加载,获取更多数据
# for i in range(5):  # 滚动5次
#     driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
#     time.sleep(2)  # 等待新数据加载

# 提取所有职位项
job_items = driver.find_elements(By.CLASS_NAME, 'con_list_item')
print(job_items)

results = []

for item in job_items:
    try:
        # 职位
        title = item.get_attribute("data-positionname")
        print(title)
        # 公司
        company = item.get_attribute("data-company")
        print(company)
        # 薪资
        salary = item.get_attribute("data-salary")
        print(salary)
        # 地点
        location = item.find_element(By.CLASS_NAME, 'add').text

        # 可能还有其他字段
        results.append({
            'title': title,
            'company': company,
            'salary': salary,
            'location': location
        })
    except:
        continue


driver.quit()

# 保存
with open('lagou_jobs.json', 'w', encoding='utf-8') as f:
    json.dump(results, f, ensure_ascii=False, indent=2)

print(f'共爬取 {len(results)} 条职位信息')

浏览器和抓取的页面数据对比

html 复制代码
//浏览器看到的页面代码
<div class="item__10RTO">
    <div class="item-top__1Z3Zo">
        <div class="position__21iOS"><span style=""><div class="position__21iOS"><div class="p-top__1F7CL"><a
                id="openWinPostion">Python开发工程师 (MJ007222)[北京·海淀区]</a></div><div class="p-bom__JlNur"><span
                class="money__3Lkgq">20k-40k</span>经验1-3年 / 本科</div></div></span></div>
        <div class="company__2EsC8">
            <div class="company-name__2-SjF"><a>微博</a></div>
            <div class="industry__1HBkr">社交媒体 / 上市公司 / 2000人以上</div>
        </div>
        <div class="com-logo__1QOwC"><img
                src="https://www.lgstatic.com/thumbnail_120x120/image1/M00/00/0D/CgYXBlTUWCCAdkhOAABNgyvZQag818.jpg"
                alt="微博"></div>
    </div>
    <div class="item-bom__cTJhu">
        <div class="ir___QwEG">
            <span>数学/统计学相关专业</span><span>社交平台</span><span>社交媒体</span><span>PHP</span><span>计算机相关专业</span>
        </div>
        <div class="il__3lk85">"核心岗位"</div>
    </div>
</div>

//抓取到的页面代码
<li class="con_list_item default_list" data-index="0" data-positionid="12196627"
    data-salary="20k-40k" data-company="微博" data-positionname="Python开发工程师 (MJ007222)"
    data-companyid="5832" data-hrid="5054402" data-adword="0">
    <span class="top_icon direct_recruitment"></span>
    <span class="top_icon school_recruitment"></span>
    <div class="list_item_top">
        <div class="position">
            <div class="p_top">
                <a class="position_link"
                   href="https://www.lagou.com/wn/jobs/12196627.html?show=c4225b1eca74480eb3a28219f055f3ff"
                   target="_blank" data-index="0" data-lg-tj-id="8E00" data-lg-tj-no="
0101
" data-lg-tj-cid="12196627" data-lg-tj-abt="dm-csearch-personalPositionLayeredStrategyNew|0"
                   data-lg-webtj-_show_id="c4225b1eca74480eb3a28219f055f3ff"
                   data-lg-webtj-_search_type="csearch" data-lg-webtj-_content_type="jd">
                    <h3>Python开发工程师 (MJ007222)</h3>
                    <span class="add">[<em>北京·海淀区</em>]</span>
                </a>
                <span class="format-time">15:13发布</span>
            </div>
            <div class="p_bot">
                <div class="li_b_l">
                    <span class="money">20k-40k</span>
                    <!--<i></i>-->经验1-3年 / 本科
                </div>
            </div>
        </div>
        <div class="company">
            <div class="company_name">
                <a href="https://www.lagou.com/gongsi/v1/334456dc004201ab6285efc5a8fcf801f68d1c0605533972.html"
                   target="_blank" data-lg-tj-id="8F00" data-lg-tj-no="
0101
" data-lg-tj-cid="5832" data-lg-tj-abt="dm-csearch-personalPositionLayeredStrategyNew|0">微博</a><i
                    class="company_mark"><span>该企业已上传营业执照并通过资质验证审核</span></i>
            </div>
            <div class="industry">
                社交媒体 / 上市公司 / 2000人以上
            </div>
        </div>
        <div class="com_logo">
            <a href="https://www.lagou.com/gongsi/v1/334456dc004201ab6285efc5a8fcf801f68d1c0605533972.html"
            "="" target="_blank" data-lg-tj-id="8G00" data-lg-tj-no="
            0101
            " data-lg-tj-cid="5832"
            data-lg-tj-abt="dm-csearch-personalPositionLayeredStrategyNew|0">
            <img src="//www.lgstatic.com/thumbnail_120x120/image1/M00/00/0D/CgYXBlTUWCCAdkhOAABNgyvZQag818.jpg"
                 alt="微博" width="60" height="60">
            </a>
        </div>
    </div>
    <div class="list_item_bot">
        <div class="li_b_l">
            <span>数学/统计学相关专业</span>
            <span>社交平台</span>
            <span>社交媒体</span>
            <span>PHP</span>
            <span>计算机相关专业</span>
        </div>
        <div class="li_b_r">"核心岗位"</div>
    </div>
</li>

总结和复盘

你遇到的问题非常典型:item__10RTO 是拉勾网使用 CSS Modules 技术生成的动态哈希类名 ,每次部署或刷新页面都可能变化。硬编码这种类名,很可能在下次访问时就失效,导致 presence_of_element_located 一直等待超时。


🔍 为什么找不到 item__10RTO
  • 类名是动态生成的
    现代前端框架(如 React、Vue)常配合 CSS Modules 使用,为每个类名添加唯一哈希值(如 item__10RTO 中的 10RTO)。这保证了样式局部作用,但使得类名不可预测每次打包都可能改变
  • 你看到的可能不是当前页面的真实类名
    如果你是从浏览器复制了昨天的类名,今天页面更新后哈希值变了,自然就找不到了。
  • 即使类名没变,也可能因为元素渲染时机或滑块遮挡导致等待超时

✅ 解决方案:使用更稳定的定位方式
方案一:等待固定的骨架类名(推荐)

从你之前提供的 HTML 可以看出,职位列表在初始加载时有一个稳定的结构:

html 复制代码
<li class="con_list_item default_list" data-positionname="Python开发工程师 (MJ007222)" ...>

这个 con_list_item 类名是固定的,而且元素在页面早期就会出现。用它来等待,既稳定又快速。

python 复制代码
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
# 等待至少一个职位项出现
item = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "con_list_item")))

如果这个也超时,说明页面可能被滑块拦截或根本没加载列表,需要先处理滑块。

方案二:基于文本内容定位

如果担心类名变化,可以用职位标题中的关键字(如"Python开发工程师")来定位:

python 复制代码
item = wait.until(EC.presence_of_element_located(
    (By.XPATH, "//*[contains(text(), 'Python开发工程师')]")
))

这种方法不依赖类名,只要页面上有该文本就能找到。但可能匹配到多个元素(比如职位名称、公司名称),你可以结合上下文进一步筛选。

方案三:使用稳定的属性

初始骨架中的 data-* 属性非常稳定,你可以直接用 XPath 通过属性值定位:

python 复制代码
item = wait.until(EC.presence_of_element_located(
    (By.XPATH, "//li[@data-positionname='Python开发工程师 (MJ007222)']")
))

不过这种方式需要知道精确的职位名称,适合单个职位的精确查找。


🧪 调试步骤:先确认页面到底有没有 item__10RTO
  1. 在浏览器中手动检查当前类名

    打开拉勾网职位列表页,按 F12 打开开发者工具,用元素选择器点一个职位项,看它的实际类名是什么。如果仍然是 item__10RTO,说明哈希没变;如果变成了 item__1aBcD,那你就需要更新代码。

  2. 在 Selenium 中打印页面源码

    在等待超时后,打印当前页面的部分源码,看看里面是否包含 item__10RTO

    python 复制代码
    try:
        item = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'item__10RTO')))
    except Exception as e:
        print(driver.page_source[:2000])
        driver.save_screenshot('debug.png')
        raise e

    如果源码中没有 item__10RTO,说明类名确实变了;如果源码中有,但依然超时,可能是元素不可见或被遮挡(但 presence_of_element_located 只检查 DOM 存在,不关心可见性)。

  3. 检查滑块是否已完全消失

    如果滑块还在,职位列表可能被遮住但 DOM 中其实存在。确保滑块消失后再等待:

    python 复制代码
    WebDriverWait(driver, 10).until(EC.invisibility_of_element_located((By.ID, "nc_1_n1z")))
  4. 检查是否有 iframe

    职位列表可能在 iframe 内,需要先切换:

    python 复制代码
    iframes = driver.find_elements(By.TAG_NAME, 'iframe')
    if iframes:
        driver.switch_to.frame(iframes[0])

💡 长期建议:放弃哈希类名,拥抱稳定结构

拉勾网的职位列表在初始骨架阶段提供了非常干净的 data-* 属性,你可以直接从这些属性中提取信息,完全不需要等待最终渲染的复杂结构。这样不仅定位简单,而且数据完整。

python 复制代码
# 等待至少一个职位项(用固定类名)
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, "con_list_item")))

# 获取所有职位项
items = driver.find_elements(By.CLASS_NAME, "con_list_item")
for item in items:
    position_name = item.get_attribute("data-positionname")
    salary = item.get_attribute("data-salary")
    company = item.get_attribute("data-company")
    print(position_name, salary, company)

这种方法避开了动态渲染的复杂性,稳定高效。


🚀 终极方案:直接调用 Ajax 接口

如果 Selenium 的稳定性始终困扰你,可以考虑放弃渲染,直接模拟拉勾网的 Ajax 请求。职位数据接口通常是:

复制代码
POST https://www.lagou.com/jobs/positionAjax.json

需要携带正确的 Cookie(可手动登录后复制)和请求头。这样既快又不会遇到滑块和动态类名的问题。


总结
  • item__10RTO 是动态哈希类名,不可靠
  • 改用固定类名 con_list_item 或基于文本/XPath 定位。
  • 先确保滑块消失,再等待列表元素。
  • 如果仍然超时,打印源码和截图,检查实际页面结构。
  • 考虑直接调用 Ajax 接口,彻底规避 Selenium 的渲染问题。

希望这些方法能帮你解决问题!如果调试后仍有困难,欢迎提供打印的源码片段,我们一起分析。

相关推荐
前端付豪2 小时前
自动学习建议解决薄弱知识点
前端·python·openai
deephub2 小时前
LangGraph vs Semantic Kernel:状态图与内核插件的两条技术路线对比
人工智能·python·深度学习·大语言模型·agent
与虾牵手2 小时前
多轮对话 API 怎么实现?从原理到代码,踩完坑我总结了这套方案
python·aigc·ai编程
geovindu2 小时前
python: Simple Factory Pattern
开发语言·python·设计模式·简单工厂模式
桃花键神2 小时前
亮数据产品实测:爬虫API、网页抓取API与代理服务器,到底该怎么选?
爬虫·亮数据
lihaiting12 小时前
css面试题
前端·css·css3
xixixin_2 小时前
【CSS】Ant Design 按钮点击时文字位移问题
前端·javascript·html
weixin199701080162 小时前
开山网商品详情页前端性能优化实战
java·前端·python
前端付豪2 小时前
实现学习报告统计面板
前端·python·llm