You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

BeautifulSoup爬取Upwork职位标题返回空列表如何解决

问题根因
  • 基础反爬拦截:裸requests请求未携带合法浏览器标识,Upwork会直接返回安全验证页或空骨架页面,返回的HTML内容和浏览器中看到的职位搜索页完全不同,自然无法匹配目标节点。
  • 动态渲染逻辑:Upwork职位列表为前端JavaScript异步拉取数据后动态插入DOM的内容,首次请求返回的静态HTML中不包含职位信息节点,静态解析初始HTML必然拿不到结果。
  • 类名依赖脆弱:硬编码的多类名组合my-0 p-sm-right job-tile-title中,部分类名是前端构建生成的可变类名,随站点版本迭代可能随时变更,匹配逻辑鲁棒性极差。
可落地方案

方案1:携带合法请求头调用后端接口(轻量高效,推荐)

直接请求职位搜索的后端接口拿结构化JSON数据,不需要解析HTML,稳定性更高。

import requests

# 替换为你本地浏览器的实际请求头,模拟真实访问绕过基础反爬
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Accept": "application/json, text/plain, */*",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "Referer": "https://www.upwork.com/nx/jobs/search/?q=web%20scraping&sort=recency",
    # 若请求返回403,可补全你登录Upwork后的Cookie字段,登录态下请求通过率更高
}

search_params = {
    "q": "web scraping",
    "sort": "recency",
    "page": 1,
    "per_page": 20
}

# 接口路径可通过浏览器F12网络面板筛选XHR请求获取,随站点版本更新可能调整
resp = requests.get("https://www.upwork.com/api/v3/jobs/search", headers=headers, params=search_params)
resp.raise_for_status()
job_result = resp.json()

# 直接从结构化数据提取标题,无需依赖DOM类名
for job in job_result["results"]["jobs"]:
    print(job["title"])
  • 抓取时建议设置3-5秒的请求间隔,避免高频请求触发IP封禁。

方案2:浏览器自动化渲染抓取(适配动态页面,逻辑简单)

如果接口参数加密难以调试,可以用自动化浏览器工具加载页面,等JS完成动态渲染后再解析DOM,和手动浏览页面的加载逻辑完全一致。

from bs4 import BeautifulSoup
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    # headless设为False可打开浏览器窗口查看实际加载过程
    browser = p.chromium.launch(headless=True)
    page = browser.new_page(user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36")
    page.goto("https://www.upwork.com/nx/jobs/search/?q=web%20scraping&sort=recency")
    # 等待职位标题节点加载完成,确保动态内容渲染完毕
    page.wait_for_selector("h4.job-tile-title", timeout=10000)
    page_source = page.content()
    browser.close()

soup = BeautifulSoup(page_source, "lxml")
# 匹配类名时只保留稳定的业务类名job-tile-title,去掉可变的样式类名
title_list = soup.find_all("h4", class_="job-tile-title")
for title in title_list:
    print(title.get_text(strip=True))

内容的提问来源于stack exchange,提问作者Wisam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 02:03:42