资讯详情

Selenium爬取京东商品信息实战:登录态、等待策略与数据清洗

📅 2026/10/3 14:10:22 | 华诺云谱 👁 阅读
Selenium爬取京东商品信息实战:登录态、等待策略与数据清洗
简介Selenium作为Web自动化利器能模拟用户操作应对动态加载页面这份资源正是基于该技术的京东商品信息爬取示例。压缩包面向Python爬虫初学者及需要处理异步渲染、登录交互的开发人员通过一个可运行的Python脚本与配套文本文件展示从驱动配置到数据落地的完整链路。包内共2个文件含1个.py脚本和1个.txt数据文件整体体积仅2KB但麻雀虽小五脏俱全脚本中演示了WebDriver的启动与访问、通过CSS选择器或XPath定位商品名称/价格、利用WebDriverWait显式等待动态元素出现以及翻页时点击“下一页”并逐页采集的思路。同时加入了time.sleep延时等基础反爬策略便于理解如何降低被封风险。txt文件可用于记录抓取结果方便后续分析。目前已有245人学习下载适合做Selenium入门练手也能为电商数据采集项目提供参考模板。需提醒的是实际应用要尊重网站Robots协议合理控制抓取频率。1. 用 selenium 爬取京东商品信息为什么开箱即跑反而最容易翻车“selenium爬取京东商品信息.zip”解压后多数人第一反应是装依赖、跑 main.py。结果十有八九死在前三分钟Chrome 弹窗一闪而过接着是无休止的滑块验证码或者列表页能开但价格字段全是空。这套方案本身的定位很明确在你不方便直接调接口的场景下用 selenium 自动化测试框架替真人完成浏览、滚动、点击和取值把京东商品信息落到结构化文件再打包成 zip。它适合做价格监控、竞品选品分析也适合第一次接触 selenium 的工程师拿真实站点练手。但这门技术真正值钱的不是“会打开页面”而是把登录态持久化、等待渲染、反爬对抗、数据清洗这四个环节串成一条能反复重跑的流水线。2. 环境与登录态先让 Selenium 驱动的 Chrome 看起来像一个真人打开 zip 包的人第一关往往不是代码逻辑而是本地环境。Selenium 不像 requests 那样装完就能跑它需要一套完整的浏览器驱动链Chrome 本体、Chromedriver、Python 绑定库三者版本要能对上。很多项目包里的 requirements.txt 只写了 selenium但没写 Chromedriver 怎么放于是新手常遇到“明明装了 selenium却报 WebDriverException: unknown error: cannot find Chrome binary”。2.1 环境准备Chromedriver 版本匹配、虚拟环境与解压路径先把依赖装到干净的虚拟环境里。以 Python 3.10 及以上、Selenium 4.x 为例python -m venv .venv # Windows 注意激活方式 .venv\Scripts\activate pip install selenium4.1 pandas webdriver-manager装完后不要直接跑爬虫先确认 Chrome 和 Chromedriver 的版本是否匹配。Selenium 4 用官方 driver 时Chromedriver 的大版本必须和 Chrome 一致否则会抛 session not created 一类的报错。Windows 下查版本最直接的方式# 在 Chrome 地址栏输入 chrome://version 看主版本号 # 或者用命令行查注册表 reg query HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon /v version然后用 webdriver-manager 自动拉取对应版本的驱动省去手工下载的麻烦from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager options webdriver.ChromeOptions() driver webdriver.Chrome(serviceService(ChromeDriverManager().install()), optionsoptions)这段代码的意思是webdriver-manager 会根据当前 Chrome 版本去下载匹配的 Chromedriver 并缓存到本地之后每次启动都复用缓存。这个做法适合个人电脑快速跑通。它的问题在于第一次需要联网下载二进制而且下载源如果被墙或者网络抖动容易中断。所以正式一点的项目里我一般会把 Chromedriver 固定放在项目 driver 目录下直接在代码里指定路径service Service(./driver/chromedriver.exe)路径这块有个血泪经验zip 解压后不要放在带中文、空格或括号的目录里直接跑。Chromedriver 启动 Chrome 时要拼一堆参数路径带特殊字符会触发各种莫名其妙的启动失败。项目包解压后先挪到纯英文路径比如D:/jd_crawler能省掉一批批次玄学报错。2.2 登录态的三个来源手动扫码、Cookie 注入和用户目录复用京东的商品列表页未登录状态下价格经常显示为“登录查看”或者“--”评论区数据也可能被折叠。所以真正能抓到完整信息的脚本第一步是先把登录态解决掉。常见做法有三种。第一种是手动扫码然后把 Cookie 持久化到本地文件。第一次运行打开登录页人工扫码后脚本把 Cookie 存成 JSON之后每次启动直接加载。代码这样写import json import time from selenium import webdriver def wait_login_and_save(driver, save_pathjd_login.json, timeout60): 打开京东登录页人工扫码后自动保存 Cookie driver.get(https://passport.jd.com/new/login.aspx) deadline time.time() timeout while time.time() deadline: time.sleep(0.5) cookies driver.get_cookies() # 京东登录成功后 pt_key 一定会出现用它当判据 if any(c.get(name) pt_key for c in cookies): with open(save_path, w, encodingutf-8) as f: json.dump(cookies, f, ensure_asciiFalse) return True return False这段代码的逻辑是不断获取当前域下的 Cookie发现关键登录字段pt_key出现就落盘。保存不只是存字符串而是把整个 Cookie 数组序列化这样后续 add_cookie 时能拿到完整的 name、value、domain、path。第二次运行加载 Cookiedef load_cookies(driver, save_pathjd_login.json): with open(save_path, r, encodingutf-8) as f: cookies json.load(f) # 先打开主站域名否则 add_cookie 会被浏览器拒绝 driver.get(https://www.jd.com/) for c in cookies: # 剔除过期时间字段有些 Selenium 版本会拦截过期的 expiry valid {k: c[k] for k in (name, value, domain, path) if k in c} driver.add_cookie(valid) driver.refresh()注意必须先driver.get(https://www.jd.com/)让浏览器处于京东域名内才能 add_cookie。直接在一个空标签页写 Cookie 会报InvalidCookieDomainException。另外Cookie 里如果有 expiry 字段且时间已过期Selenium 4 的 add_cookie 会直接抛异常所以加载时要过滤掉。第二种是复用 Chrome 的用户数据目录。启动时给 Chrome 指定一个固定的 profile 路径登录状态会被浏览器持久化保存options.add_argument(r--user-data-dirD:/jd_profile)这种方式的好处是连滑块验证后的状态都能保留坏处是 profile 目录会越来越大而且如果上一次 Chrome 没有正常退出下次启动会报锁冲突。我一般只在本地调试时用它线上跑任务还是采用 Cookie 注入。2.3 等待策略隐式等待、显式等待和固定 sleep 怎么配合京东商品页是异步渲染的DOM 结构先出来价格和销量是后面通过 ajax 填进去的。如果 Selenium 定位元素时页面还没渲染完拿到的就是空文本。新手最常见的写法是driver.implicitly_wait(10)然后直接 find_element结果偶尔有值、偶尔为空。三类等待方式各有各的适用场景等待方式生效范围典型坑time.sleep(n)当前线程固定停 n 秒网络波动时不够用网络好时浪费时间driver.implicitly_wait(n)全局所有 find 操作对 ajax 动态插入的节点失效且和显式等待混用会翻倍等待WebDriverWait显式等待单次条件判断条件选错照样拿空比如 presence 不等于 visible我推荐把显式等待作为主方案。取价格时明确等待价格节点可见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, timeout15, poll_frequency0.3) price_el wait.until( EC.visibility_of_element_located((By.CSS_SELECTOR, span.p-price i)) )timeout15表示最多等 15 秒poll_frequency0.3表示每 0.3 秒查一次条件。用visibility_of_element_located而不是presence_of_element_located是因为价格节点可能在 DOM 里存在但还没有被渲染出文本只有 visible 才代表用户真正看得到内容。固定 sleep 只用于必须等待的场合比如滑块验证出现后等它稳定或者滚动加载间歇不要全篇到处 sleep。3. 商品页 DOM 拆解定位商品容器、价格与评价数的几种写法登录态和等待策略就位后才算进入真正的爬取环节。京东的商品列表页结构看着规整但前端改版频繁同一个字段的 class 名说变就变。所以这一章要解决的核心问题不是“用什么选择器”而是“怎么写出不容易一改版就全挂的选择器”。3.1 列表页结构观察从大容器到商品块的层级关系先打开搜索页按 F12 看 Elements 面板。京东 PC 端的搜索结果有一个大容器商品列表项在容器内部平铺。过去多个版本里相对稳定的层级是div#J_goodsList └─ ul.gl-warp └─ li.gl-item#J_goodsList是商品列表的外层li.gl-item是每个商品项。写定位时不要从全局直接find_elements(By.CLASS_NAME, gl-item)因为页面上可能有其他区块也用了类似 class。更保险的做法是先锁容器再在容器内找子节点container driver.find_element(By.CSS_SELECTOR, div#J_goodsList) items container.find_elements(By.CSS_SELECTOR, li.gl-item) print(f当前页共抓取 {len(items)} 个商品容器)这段代码的意义在于缩小搜索范围。Selenium 的 find_element 是全局查找万一页面其他地方出现同名 class就会混入非商品节点。先拿容器再往内找逻辑更清晰也方便后续按商品块做相对定位。3.2 相对 XPath每个字段都在商品块内部继续找拿到li.gl-item后商品名称、价格、店铺、评论数都在这个 li 内部。用相对 XPath 写是最直观的for item in items: try: name item.find_element(By.XPATH, .//div[contains(class, p-name)]/em).text except Exception: name try: price item.find_element(By.XPATH, .//div[contains(class, p-price)]//i).text except Exception: price try: shop item.find_element(By.XPATH, .//div[contains(class, p-shop)]/a).text except Exception: shop try: commit item.find_element(By.XPATH, .//div[contains(class, p-commit)]/a).text except Exception: commit 这里每个 XPath 都以.//开头意思是“从当前节点内部继续查找”。这是最容易踩的坑如果写成//div[classp-name]XPath 会从整个页面根节点重新搜索结果每个商品块取到的都是第一个商品的名字。contains(class, p-name)的目的是避开 class 属性的顺序变化比如classp-name p-name-special和classp-name都能匹配。3.3 滚动加载、懒加载与翻页时机把控京东搜索列表默认只加载前几十个商品往下滚动才会触发懒加载。如果脚本只抓第一屏数据量非常有限。手动触发滚动的方式import random import time def scroll_to_load(driver, max_scrolls8, base_pause1.2): last_height driver.execute_script(return document.body.scrollHeight) for _ in range(max_scrolls): driver.execute_script(window.scrollTo(0, document.body.scrollHeight)) time.sleep(base_pause random.random()) new_height driver.execute_script(return document.body.scrollHeight) if new_height last_height: break last_height new_heightbase_pause random.random()是为了让滚动间隔不完全一致避免被行为检测判断为机械操作。document.body.scrollHeight返回页面总高度如果滚动后高度不再变化说明已经滚到底了可以直接 break。翻页方面京东搜索页的翻页既可以点击下一页按钮也可以直接改 URL 里的page参数。用 URL 方式更快但要注意关键词需要 URL 编码from urllib.parse import quote keyword 手机 for page in range(1, 4): url fhttps://search.jd.com/Search?keyword{quote(keyword)}page{page} driver.get(url) time.sleep(2) ... # 抓取当前页数据这个小结的要点是不要抓完一页立刻翻下一页中间至少要留 2 秒以上间隔否则触发风控的概率会明显上升。4. 数据清洗与打包交付从零散字段到可复用的 CSV 和 zip抓下来的数据不会直接能用。京东页面上显示的是“1,299.00”或者“满199减30”这些字符串直接写进 Excel 就是灾难。真正的交付物应该是一份字段干净、去重可靠、可以直接分析的 CSV再打包成 zip 发给别人或存档。4.1 价格与促销文案的清洗把“满减”“折扣”“暂无报价”挡在门外第一步先写一个清洗函数把各种展示型价格字符串转成 floatimport re def clean_price(raw: str): if not raw or not isinstance(raw, str): return None text raw.strip() if any(k in text for k in (暂无, 到货, 预约)): return None # 促销文案里常见“满199减30”“3折起”这些不是真实价格 if any(k in text for k in (折, 满, 减, 券)): return None digits re.sub(r[^\d.], , text) try: return float(digits) except ValueError: return None正则re.sub(r[^\d.], , text)会把“1,299.00”里的逗号去掉变成1299.00。但要注意“1299.00”这种字符串转 float 后是浮点数后续如果要入库建议保留原始字符串字段方便追溯。这里判断“折”和“满减”很重要因为“199.00”放进正则过滤后也能解析出数字但它是促销门槛不是成交价。4.2 用 SKU 做唯一主键去重、补缺和类型检查京东商品的链接里有固定的 SKU 编号比如https://item.jd.com/100027319581.html。这个数字是商品唯一标识抓取时应该把商品链接一并存下来然后从链接里提取 SKU 作为主键。import pandas as pd rows [] # 每个元素是 dict包含 name, price_raw, url 等字段 df pd.DataFrame(rows) df[sku_id] df[url].str.extract(r/(\d{6,})\.html) df df.dropna(subset[sku_id]) df[price] df[price_raw].map(clean_price) df df.drop_duplicates(subset[sku_id], keepfirst)str.extract用正则从 URL 中捕获连续 6 位以上的数字作为 SKU。drop_duplicates(subset[sku_id])保证了同一个商品不会重复出现。这里有一个常用细节如果同一次运行里页面翻了好几页同一个商品可能出现在多个页去重逻辑必须放在清洗函数执行之后因为两个页面的价格可能一个是到手价一个是原价先清洗再按 SKU 去重保留第一个即可。4.3 zip 打包与文件名编码别让交付物落到别人手里再翻车数据清洗完导出 CSV建议直接用utf-8-sig编码。utf-8-sig会在文件开头写入 BOM 头Windows 上的 Excel 打开 UTF-8 编码的 CSV 时才不会中文乱码。然后用 zipfile 打包成最终交付物import zipfile with zipfile.ZipFile( jd_goods_export.zip, w, zipfile.ZIP_DEFLATED, compresslevel6 ) as zf: zf.write(jd_goods.csv, arcnamedata/jd_goods.csv) zf.write(抓取日志.txt, arcnamelog/crawler_log.txt)ZIP_DEFLATED是常见压缩算法compresslevel6在速度和压缩率之间取平衡。arcname这个参数尤其要注意它决定文件在 zip 包内的路径。如果arcname带中文Python 3.11 以下的 zipfile 会把文件名按 GBK 编码写入部分解压工具解出来是乱码。我的习惯是 arcname 全部用英文路径例如data/jd_goods.csv给别人的时候再附一个 README 说明字段含义。5. 避坑手工爬京东最容易撞的五个坑逐个说透这套流程跑下来真正让人心态崩掉的往往不是代码语法而是运行时各种间歇性故障。下面这几个现象我都在不同阶段遇到过每条按“现象→原因→解决”讲清楚。5.1 现象抓了二十分钟突然弹滑块验证码整批任务卡死这是做京东爬取最容易遇到的事。脚本跑得好好的突然页面弹出滑块验证之后find_element全部超时任务挂在半路。原因请求频率太高风控模型判定当前浏览器行为不像真人典型的触发点是连续翻页无间隔、多个商品块之间的抓取时间间隔过短。解决把每次翻页间隔拉长到 3 秒以上并且在每抓一批后随机跳到页面中部模拟浏览动作。更关键的是做断点重试把已经抓到的数据实时写入 CSV任务崩溃后从上次成功的页码续跑而不是从头再来。没有断点续跑的爬虫一旦中途崩掉就是纯浪费时间。5.2 现象第一次运行有数据第二次运行整个列表为空第二次跑的时候页面能打开但商品容器li.gl-item一个都找不到。原因最常见的是登录态失效。京东的 Cookie 有效期不是永久的隔几个小时后pt_key过期未登录状态下搜索页可能跳转到登录页或者返回空列表。另一种可能是搜索关键词本身没有结果比如商品下架。解决启动流程里先做一个探活判断抓取前打开商品详情页试试能否看到价格文本如果看不到就重新走扫码登录流程。不要等抓到一半才发现数据全空。5.3 现象多线程抓取时价格错乱商品名配到了别人的价格图省事开了ThreadPoolExecutor把同一个 WebDriver 实例传给多个线程结果数据张冠李戴。原因Selenium 的 WebDriver 不是线程安全的。多个线程同时调用同一个 driver 的execute_script和find_element命令会交叉发送到同一个浏览器会话返回结果互相覆盖。解决多线程抓取时每个线程创建一个独立的 WebDriver 实例并限制线程数不超过 3 到 4 个。还要错开各线程的启动时间避免同时发起大量请求。更稳妥的方式是改用单线程加异步用 asyncio 控制并发粒度。爬取类任务并发不是越大越好宁可慢一点也不要把 IP 拖进风控黑名单。5.4 现象页面滚动后价格被异步重绘拿到的是旧值滚动加载后JS 会把部分商品的价格重新渲染但页面里同时存在旧价格节点和新价格节点Selenium 定位到的是隐藏的旧节点。原因京东前端在做价格更新时不是替换文本而是插入新节点并隐藏旧节点。旧的span.p-price i仍然在 DOM 里只是藏在不可见区域。解决取价前加一个显式等待判断价格文本已经变化from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By wait WebDriverWait(driver, 8) wait.until( lambda d: d.find_element(By.CSS_SELECTOR, span.p-price i).text not in (, ) )这样能确保读到的是渲染完成后的实际价格。如果还发现价格错乱就把当前页面的截图保存下来肉眼对比一下节点比猜原因快得多。5.5 现象解压 zip 后 CSV 中文乱码或者直接报 BadZipFile交付给同事的 zip在对方电脑上解压时报“文件损坏”或者要求输入密码但你自己解压没问题。原因一是 CSV 编码用了普通 utf-8Windows 解压工具解出来再打开Excel 按 GBK 解析导致乱码二是 zip 文件本身存在伪加密。所谓伪加密就是 zip 头部的通用标志位被置位成“有密码”但数据区并没有真正做加密处理。某些压缩工具生成的伪加密 zip在标准解压工具里会被当成加密文件弹出密码框。解决CSV 导出统一用utf-8-sig。遇到 BadZipFile 或提示密码时用 Python 先检查 zip 条目的加密标志字import zipfile with zipfile.ZipFile(jd_goods_export.zip) as zf: for info in zf.infolist(): print(info.filename, hex(info.flag_bits))flag_bits的第 0 位是加密标志位。如果该位为 1 但文件数据未实际加密属于伪加密可以直接用工具修复标志位后解压。不要直接拿这个 zip 去问同事要密码先确认是不是伪加密能省掉很多沟通成本。6. 让它变成能自查的幂等任务缓存、断点重跑和最终校验最后一个进阶习惯把爬虫当作数据处理流水线来设计而不是一次性脚本。我自己维护的抓取任务里最值钱的一段不是怎么定位元素而是怎么在一次次失败后快速恢复。第一个做法是按 SKU 做本地缓存。每次抓到一个商品详情就把它写入cache/{sku_id}.json。下次再跑时如果缓存文件存在直接跳过网络请求import os import json def get_from_cache(sku_id, cache_dircache): path os.path.join(cache_dir, f{sku_id}.json) if os.path.exists(path): with open(path, r, encodingutf-8) as f: return json.load(f) return None这样同一个商品不会因为重复抓取而被重复请求也能避免数据错乱。更重要的是有了缓存脚本中断后再次启动已经抓过的 SKU 不会重新消耗请求配额。第二个做法是记录每个分页的完成状态。每抓完一页在progress.json里更新页码和时间。日志里记录清楚每一行的来源页码。重跑时跳转到未完成的页码即可而不是从第一页重新开始。第三个做法是完成后校验结果文件。不要只抓完就算结束校验一下 CSV 行数和抓取的商品容器数是否一致价格字段有没有异常缺失。这个校验逻辑写成一个简单函数df pd.read_csv(jd_goods.csv) assert len(df) 0, 没有抓到任何商品 assert df[sku_id].is_unique, 存在重复 SKU assert df[price].notna().sum() len(df) * 0.8, 价格缺失过多这三条断言分别检查空数据、重复数据和价格缺失率。校验跑不过就说明某段链路有问题先修完再交付。这套思路让我在爬虫崩溃后的恢复时间从半小时压到两分钟希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑