
简介这是一份演示如何用Selenium自动化抓取京东商品信息的入门级代码包面向Python爬虫初学者或需要处理动态加载页面的开发者。压缩包体积仅2KB包含1个Python脚本和1个txt数据文件脚本主体覆盖WebDriver初始化、商品页面跳转、通过CSS选择器或XPath提取名称与价格、借助WebDriverWait显式等待动态元素出现以及模拟点击翻页遍历多商品等关键环节。txt文件则用于保存或整理抓取结果整体结构简洁便于逐行对照学习。资源虽然精简但将Selenium最常用操作浓缩在一个示例中同时附带延时控制、随机User-Agent等反反爬思路可帮助读者理解浏览器自动化与网页数据采集的实际配合方式。当前已有245人学习下载适合用来熟悉基础爬虫流程并可作为后续扩展到多线程、代理IP或数据清洗项目的参考起点。1. 用 Selenium 抓京东商品为什么这个方案到现在还有人用做竞品比价、选品分析或者品牌舆情监控的人早晚都会遇到同一个问题拿不到商品数据。京东的反爬不像某些小站点那样形同虚设它的动态渲染、异步加载和风控策略都在升级但它又不像有些平台那样完全封死自动化。于是 Selenium 这种笨办法反而成了绕不开的兜底方案——不依赖任何私有 API不破解任何签名就是开一个真实浏览器窗口按部就班地操作拿到的是完全真实的数据流。这套方案适合两类人一是短期抓几十页、几百个商品做分析不值得引入重型分布式爬虫框架的团队二是被接口反爬逼到墙角只有 Selenium 还能稳定出数的场景。它的核心价值就一句话用大体量浏览器的仿真行为换取可控的反爬代价。下面我把从环境准备到字段清洗的完整路径拆开讲。2. 环境准备与驱动匹配这里浪费的时间比写爬虫本身还多2.1 浏览器版本和 Driver 版本对不上Selenium 直接给你脸色看先说一个反直觉的事实Selenium 本身只是个协议层真正干活的是浏览器驱动。装 Selenium 只需要一条命令但驱动和浏览器版本不匹配会让你在启动阶段就翻车而且报错信息特别有迷惑性常见的是session not created或者unknown error: DevToolsActivePort file doesnt exist。我见过太多人把时间浪费在重装 Selenium 上实际上问题出在 Chrome 和 chromedriver 的版本大版本号不一致。我是这么处理的先打开 Chrome 的「关于」页面看版本号再去对应版本的驱动下载页拿同版本的驱动。这里有个小技巧驱动下载页的文件名里会带版本号比如chromedriver_mac_arm64这种别下错了平台。如果你用的是 Linux 服务器还得注意是 64 位还是 32 位现在基本全是 64 位了。# 建议用 webdriver-manager 自动匹配别再手动下驱动了 from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager options webdriver.ChromeOptions() options.add_argument(--headlessnew) # 新无头模式反检测能力比旧版强 options.add_argument(--disable-gpu) options.add_argument(--no-sandbox) # root 用户跑 CI 必须加 # 禁用自动化控制标志降低被识别为 Selenium 的概率 options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionsoptions) driver.get(https://www.jd.com) print(driver.title)这段代码的核心是ChromeDriverManager().install()它会自动读取你本机 Chrome 的版本号然后下载对应版本的驱动从根上消除版本不匹配问题。--headlessnew是新版 Chrome 109 才有的无头模式用它不是因为快而是因为它的指纹特征和真实浏览器更接近被检测的概率比老无头模式低。excludeSwitches和useAutomationExtension这两个参数是去掉navigator.webdriver标记的第一道防线后面还会讲到别的。注意--headlessnew的兼容性需要 Chrome 109 以上如果你还在用老版本去掉这个参数直接用--headless但要做好被反爬识别的心理准备。2.2 京东页面加载机制Selenium 等的是什么你不等就白等京东的搜索页不是一打开就有数据的。页面先加载骨架结构然后通过 JavaScript 异步请求商品数据最后才渲染到 DOM 上。这里有个关键点你用driver.get()返回时页面大概率只加载了一部分商品列表可能还是空的。如果你立刻去定位元素会拿到空节点。所以 Selenium 方案里最重要的一步是显式等待。别用time.sleep(5)这种固定等待网络慢的时候 5 秒不够网络快的时候纯浪费 3 秒。正确做法是用WebDriverWait加条件等到目标元素出现再动手。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By url https://search.jd.com/Search?keyword机械键盘encutf-8wq机械键盘 driver.get(url) # 等待商品列表容器出现超时 20 秒 wait WebDriverWait(driver, 20) cards wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, #J_goodsList li.gl-item)) ) print(f第一页商品数量: {len(cards)})presence_of_all_elements_located等待的是 DOM 里出现了这些节点但注意它不保证价格已经渲染完成——京东的商品价格经常比标题晚一步出现。所以后面解析字段时对价格还要再单独等待一次不能想当然地认为列表出现就万事大吉。这里的wait对象是复用的后续每一步都用它来等而不是每次新建逻辑会更清晰。3. 解析商品卡片与字段清洗拿到原始数据只是第一步3.1 CSS 选择器定位京东前端改版后的稳定锚点京东搜索页的商品卡片结构相对稳定核心字段的锚点是多年没怎么动的每个商品项在#J_goodsList下的li.gl-item里标题在.p-name下的em标签店铺名在.p-shop下的a标签。这些 selector 我用了一年多京东前端改过几次版这些锚点都没变过算是比较靠谱的抓取目标。但有几个细节需要注意。第一li.gl-item里可能混入广告位这些广告的 DOM 结构和普通商品一样但会多一个>from selenium.webdriver.common.by import By import re def parse_card(card): 从单个商品卡片中提取标题、价格、店铺、链接 # 标题在 .p-name em 里去掉换行和多余空格 title_node card.find_element(By.CSS_SELECTOR, .p-name em) title re.sub(r\s, , title_node.text).strip() # 价格可能在 .p-price strong i 里也可能是异步填充给 3 秒缓冲 try: price_node card.find_element(By.CSS_SELECTOR, .p-price strong i) price float(price_node.text) except ValueError: price None # 价格还没渲染出来标记为缺失 # 店铺名可能为空对应京东自营 shop_node card.find_elements(By.CSS_SELECTOR, .p-shop a) shop shop_node[0].text if shop_node else 京东自营 # 商品链接在跳转链接的 href 里用它反查 sku link_node card.find_element(By.CSS_SELECTOR, .p-img a) link link_node.get_attribute(href) sku_match re.search(r/(\d)\.html, link) sku sku_match.group(1) if sku_match else None return { sku: sku, title: title, price: price, shop: shop, link: link, }这里有个取舍问题为什么用 CSS 选择器而不用 XPath京东的 DOM 层级里 XPath 的路径经常变CSS 选择器是直接按 class 命中改动少一个层级就少一次维护成本。这个函数的输入是一张商品卡片元素输出是结构化字典后续不管是写 CSV 还是入 MySQL 都方便。把解析写成纯函数还有个好处——你可以在真实页面上拷一段卡片 HTML 下来本地用 pytest 反复测不用每次都跑一遍浏览器。3.2 翻页的四个坑页码从 2 开始、s 参数、点击还是跳转、请求频率京东搜索页的翻页逻辑藏了几个坑。第一URL 里的 page 参数不是从 1 开始的第 1 页不传 page第 2 页是page2第 3 页是page3以此类推。第二有些页面会带s参数和click参数这俩是京东用于追踪搜索行为的直接构造 URL 不要求必须带。第三你直接通过driver.get()跳转到page5页面可能实际渲染的还是第 1 页内容——因为数据是异步加载的URL 变了但内容还没来得及请求。这时候最稳的办法不是跳转而是用 Selenium 在页面底部点击「下一页」按钮。def next_page(driver, wait): 点击翻页按钮返回 True 表示翻页成功False 表示没有下一页 try: # 京东的翻页按钮在 .page-nav 区域注意总是一大排数字 next_btn wait.until( EC.element_to_be_clickable( (By.CSS_SELECTOR, .pn-next) ) ) next_btn.click() # 翻页后强制等待商品列表刷新价格节点是重新创建的 wait.until( EC.presence_of_all_elements_located( (By.CSS_SELECTOR, #J_goodsList li.gl-item) ) ) return True except Exception: return False点击翻页有个副作用京东每翻几页会弹出一个登录框这个框会打断你的点击流程。所以每次翻完页我都要顺手检查一下有没有遮罩层有就关掉。另外翻页次数多了以后会出现验证码跳转页面这时候再点下一页就没反应了需要单独处理。以后再讲验证码的问题这里先记住翻页本身不是问题翻页触发风控才是问题。我一般在翻到第 5 页以后在两次请求之间加一个随机等待 3 到 7 秒打乱请求节奏。3.3 数据落盘CSV 和 Excel 的编码坑、字段边界、增量去重抓到的数据最终要落盘。很多人的第一反应是存 Excel但 Selenium 抓取通常是一次性跑几千条用 openpyxl 写 Excel 又慢又容易崩。我的习惯是先用 CSV 做中间存储后续有需要再转 Excel这样数据出了任何问题都好排查。写 CSV 时第一个坑就是编码。Excel 打开 UTF-8 编码的 CSV 文件中文全是乱码。解决办法是写入时用utf-8-sig也就是带 BOM 的 UTF-8Excel 才能正确识别。第二个坑是换行符——商品标题里可能带着\n或者\r直接写会把一行数据拆成两行。所以在写入前所有字符串字段都要把\n和\r替换成空格。import csv from pathlib import Path def save_to_csv(items, filepath): 增量追加写入 CSV用 sku 做去重判断 filepath Path(filepath) exists filepath.exists() with open(filepath, a, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[sku, title, price, shop, link]) if not exists: writer.writeheader() for item in items: # 字段清洗去掉换行避免破坏 CSV 结构 item[title] item[title].replace(\n, ).replace(\r, ) writer.writerow(item)newline这个参数容易被忽略——不加的话在 Windows 上写 CSV 会在每行后面多一个空行读回来时每行都带着\r\r\n的残留。encodingutf-8-sig是给 Excel 打开用的如果后续程序读取用utf-8打开也没问题。去重逻辑上增量写入时用sku做唯一键每次抓完一批先读已有的 CSV 收集 SKU 集合新数据里sku已经被抓过就跳过避免跑批任务时重复数据越堆越多。4. 风控识别与反检测策略为什么你的 Selenium 一抓就挂4.1 京东靠什么认出 SeleniumWebDriver 标记和浏览器指纹很多初学者不理解明明我用 Selenium 打开浏览器京东为什么像长了眼睛一样每次都弹验证码答案在你的 JavaScript 环境里。Selenium 控制的 Chrome 会往全局挂一个navigator.webdriver属性它的值是true。正常用户浏览器的这个值要么是undefined要么是false。网站的反爬脚本只要执行一行navigator.webdriver true就能判断你是机器人。但这只是最浅的一层。更狠的检测手段是 CDP也就是 Chrome DevTools Protocol。网站可以通过--remote-debugging-port参数检测到你开启了调试端口而 Selenium 必须要这个端口才能工作这就陷入了一个死循环。更别提一些专业的风控 SDK比如设备指纹采集它能从 Canvas 渲染、WebGL 图形、时区、字体列表里提取你的设备指纹。如果你在同一台服务器、同一个 IP 上跑了几百次 Selenium指纹一致性反而成了最大的破绽。# 启动前注入 stealth 脚本掩盖自动化特征 import time from selenium.webdriver.common.by import By options.add_argument(--disable-blink-featuresAutomationControlled) driver webdriver.Chrome(serviceservice, optionsoptions) # 在页面加载任何脚本前执行 stealth JS stealth_js Object.defineProperty(navigator, webdriver, { get: () undefined }); Object.defineProperty(navigator, plugins, { get: () [1, 2, 3, 4, 5] }); Object.defineProperty(navigator, languages, { get: () [zh-CN, zh, en] }); driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, {source: stealth_js}) driver.get(https://search.jd.com/Search?keyword机械键盘encutf-8)--disable-blink-featuresAutomationControlled这个参数能去掉大部分自动化控制标记Page.addScriptToEvaluateOnNewDocument是在页面打开前注入脚本这样即使页面之后读取navigator.webdriver拿到的也是被改写的结果。这套组合拳能应付大部分基于 JS 标记检测的反爬策略但应付不了行为分析——如果你的请求频率每分钟超过 20 次IP 地址、设备指纹、行为轨迹三个维度交叉验证照样会被识别。4.2 验证码的触发节奏识别、等待、人工介入我发现一个规律京东的验证码不是你在第 1 次请求时就触发的而是有个积累过程。刚开始几个请求完全正常第 10 个、第 20 个、第 50 个触发点没有固定规律。这个设计很聪明它让你以为风控没生效等你跑得正爽的时候一下子卡死之前的所有抓取白费。应对方式也是分层的。第一层是这个请求失败了先别急着重试停下来等 30 到 60 秒换个 User-Agent再请求一次。很多时候只是临时限流不是永久封禁。第二层是如果页面跳到了验证码页面Selenium 的当前 URL 会变成verify.jd.com这类地址你需要在代码里做一个 URL 判断。发现跳到验证码页面就立刻停手而不是傻乎乎地继续点翻页。def check_verification(driver): 检查页面是否跳转到验证码或登录拦截页 current driver.current_url if verify in current or passport in current: print(f触发风控当前 URL: {current}) return True # 检测页面里是否出现滑块验证码的 iframe try: driver.find_element(By.ID, JDJRV-wrap-login) return True except Exception: return False这里我不推荐自动化去破解滑块验证码原因有两条。第一京东的滑块验证码带有行为轨迹分析程序生成的拖拽轨迹在加速度和抖动模式上和真人差距明显硬要模拟反而更容易被标记第二这是一个军备竞赛的泥潭京东更新一次验证码策略你的破解代码就得重写一次。正确做法是把验证码识别当成一个运维事件触发后停止抓取发个通知让值班的人手动过一下或者干脆让请求频率降到底等风控冷却了再回来。4.3 请求频率与 IP 池的现实问题别指望免费方案能跑大规模讲了这么多隐藏手段最后必须泼一盆冷水Selenium 这种方案天生就不是为大规模抓取准备的。每个浏览器进程要吃几百 MB 内存一台 8G 内存的服务器最多同时跑 5 个实例每个请求都要等完整页面加载就算做无头模式抓一页也要 5 到 10 秒再加上反爬需要控制频率一个小时能抓完几千条已经算高效了。如果你有更高的量级需求比如每天抓 10 万条Selenium 就不合适了应该考虑用 requests 直接调接口拿 JSON 数据。但京东的接口有签名参数破解签名的成本也不低。所以现实的路径是小规模、低频、短周期的抓取任务用 Selenium 完全够用大规模持续抓取就应该老老实实考虑商业数据服务或者找官方合作渠道而不是在 Selenium 上硬撑。这不是技术能力问题是投入产出比的问题。5. 常见问题排查5 个把新手卡死的坑我全踩过5.1 超市里商品定位到空 list不是等待不够是页面根本没加载出来现象用find_elements(By.CSS_SELECTOR, #J_goodsList li.gl-item)拿到的列表长度是 0但你在浏览器开发者工具里明明能看到商品卡片。原因这个页面是异步渲染的。driver.get()返回不代表数据加载完成。我见过很多人在地址栏手动打开这个 URL看到内容马上就写代码结果代码运行时机比人眼慢一点点商品列表还没来得及插入 DOM。解决改成显式等待不要用隐式等待。driver.implicitly_wait(10)是给每一次查找操作加了一个兜底超时但它不会主动等待「商品列表出现」这个条件。用WebDriverWait配合presence_of_all_elements_located才能精确等对时机。另外检查一下你的网络如果公司网络访问京东本身就慢把超时时间从 10 秒放宽到 20 秒。5.2 价格字段是空字符串或 ¥ 开头解析直接报 ValueError现象price_node.text返回的不是数字而是空字符串偶尔还有¥1.00这种像占位符的数据。原因京东的商品卡片里标题、店铺、链接是首屏渲染的价格经常是异步填充的。有些商品无货时显示的不是具体价格而是「暂无报价」或者一个破折号。如果你用float(price_node.text)强转遇到非数字字符就会崩。解决解析前先做一次文本清洗把¥、逗号、空格全部去掉再用正则提取数字部分。提取不到数字就置为None同时做continue或标记缺失不要让一个坏数据毁掉整批解析。这一步我这边的经验是每 100 条里大概有 2 到 3 条价格异常算是正常范围。5.3 CSV 文件用 Excel 打开全乱码换行还错位现象encodingutf-8写入的 CSVExcel 打开后中文全是乱码或者一条商品记录被拆成了三四行。原因Excel 默认用 ANSI 编码打开 CSVUTF-8 的字节流被解释成乱码。换行错位是因为标题里有原始换行符\nCSV 的引号包裹机制没有生效。解决写入编码改成utf-8-sig同时newline防止 Windows 下出现空行。字符串字段在写入前清洗换行和回车。这个坑属于必踩项目见过一次以后就会刻在脑子里。5.4 翻页翻到一半就停了控制台报错 ElementClickInterceptedException现象跑了 3 页正常第 4 页开始点击「下一页」按钮抛异常ElementClickInterceptedException意思是元素被挡住了。原因京东的页面里有悬浮的推广遮罩层、登录弹窗或者客服气泡窗口它们覆盖在翻页按钮上方Selenium 的点击被拦截。弹窗出现时机不确定和你的抓取频率有一定相关性。解决点击翻页按钮前先尝试关闭弹窗。常见的关闭按钮 selector 是.close和#closeTANWindow。关不掉的话用 JavaScript 直接点击driver.execute_script(arguments[0].click(), next_btn)绕过遮挡层。但这只能治标遮罩层的存在本身就是一个信号——你的请求频率太快了建议把翻页间隔从 2 秒拉大到 5 秒。5.5 开了无头模式就触发验证码有头模式反而没事现象同一套代码--headlessnew跑 20 页触发验证码改成有头模式跑 50 页都正常。原因无头模式虽然有了新版本渲染引擎但它的 TLS 指纹、Canvas 渲染结果和真实浏览器还是有差异。京东的风控系统对无头环境的评分比对有头环境高尤其当你同时用了非浏览器默认的窗口尺寸时这个差距更明显。解决如果目标数据量不大直接用有头模式跑挂在服务器上别管它就行不想弹出的可以把窗口最小化。如果非要无头模式把窗口尺寸设置成1920,1080这种常见分辨率加上--langzh-CN等参数把指纹特征拉回正常区间。但坦白说无头模式想做到和有头模式一样安全目前还没有 100% 的办法。6. 进阶把详情页字段补齐用会话复用做可控的并发加速基础列表页能跑了之后你会发现商品标题、价格、店铺这些信息够做粗粒度分析但做不了深度竞品分析——缺商品规格、缺评论数、缺促销信息。这些都得进详情页拿。我一般会用列表页的 SKU 拼出详情页 URL然后复用同一个浏览器会话逐条访问。但详情页的加载更重多开两个详情页就要小心被风控盯上。我的习惯是控制并发粒度不是开 10 个 Selenium 实例而是 2 到 3 个实例每个实例通过window.open新开标签页一个标签页处理详情页一个标签页保持列表页状态。请求完就立刻关闭标签页保持整个会话的活跃度像真人浏览。实测 2 个实例、每个实例 1 个详情页标签抓取速度大约是一小时 300 个详情页睡眠间隔控制在 3 到 6 秒连续跑 4 小时不会触发验证码。一个值得注意的细节是详情页里的促销信息、优惠券倒计时是动态节点位置不固定不要用固定 CSS 锚点去定位而是用「包含文本」来匹配比如//div[contains(text(),促销)]/following-sibling::div这种相对路径。最后落库前做一次完整性校验每个商品必须同时存在 SKU、标题、价格三个核心字段缺失任何一个就打上incomplete标记让后续的数据分析流程提前知道这批数据质量不够硬。我的习惯是每次抓完数据后随机抽 5 条去页面手动核对一遍确认标题、价格、店铺都没错位。这个习惯救过我很多次——有次京东改版把店铺和广告位互换位置我的解析代码全跑偏了要不是人工核对及时发现整批数据就废了。做爬虫这件事永远给自己的数据留一条人工核验的后路不迷信代码输出。希望这些经验能帮你少走弯路。本文还有配套的精品资源点击获取