
简介面向Python学习者与毕业设计人群这套1688商品信息爬虫资源以Selenium自动化采集为核心覆盖电商数据抓取与项目报告撰写可解决商品信息批量获取、登录Cookie管理等常见问题。资源共19个文件包括7个Python脚本、1份docx项目技术报告、3份Markdown说明文档、txt依赖清单与JSON配置等压缩包约985KB目录结构清晰便于按模块快速定位。目前已有80人学习适合作为毕设、课设或技术研究的参考起点。除1688主站外还提供淘宝、京东等平台的爬虫脚本支持多平台商品数据采集配套的HAR抓取说明、项目技术报告和常见问题音频提示能够帮助读者理解环境配置与排错思路代码经过多种环境验证可直接运行或在其基础上扩展定制其他功能降低从零搭建成本。1. 1688商品信息爬虫一份能直接跑的毕设级Selenium采集工程在电商数据抓取里1688 是个典型又拧巴的目标页面大部分是动态渲染直接拿 requests 去取经常只能拿到一个带登录提示的空壳明明白白写在 HTML 里的商品标题、价格、SKU 列表几乎看不到。Selenium 自动化采集的思路不一样它把真实浏览器跑起来页面怎么渲染就怎么采改造成本比很多人想象的低。这份资源的核心就是一套能直接跑的 Python Selenium 采集脚本配套项目报告覆盖登录态复用、列表翻页、详情解析和数据落库四个环节适合正在做 python 毕设、或者想快速验证电商数据抓取效果的开发者。拿到手先别改代码把工程结构和登录逻辑看明白再跑数据你会省掉大半天的排错时间。2. 选型与工程结构Selenium 强在哪项目包解压后该有什么2.1 为什么不是 requests 而是 Selenium很多新手一上来就装 requests 加 BeautifulSoup因为入门教程里爬虫都是这么写的。真正跑到 1688 列表页时价格、销量字段全是空白因为页面主体是异步渲染接口返回的数据也被加密过你抓到的 HTML 里只有一堆空 div。这时候不是正则写得不对而是你拿到的根本不是最终 DOM。Selenium 打开的是完整浏览器等 JS 跑完再取页面文本和属性天然绕开了“渲染不完整”的问题。做这套选型之前我也试过直接抓接口但 1688 的接口签名和加密参数需要逆向签名算法还经常变用 Selenium 反而稳定得多。另一个理由是调试成本。requests 模式下写 XPath 全靠抓包猜结构看不到页面真实状态。Selenium 可以把浏览器调成非 headless 模式一边断点一边看页面变化也能随时执行 execute_script 去读某个元素的值。对毕设来说还有一个现实问题大多数老师听得懂“Selenium 模拟真实用户操作”这句话也愿意听你在验证码、登录态、翻页逻辑上踩过的坑但“我用 requests 逆向了一个加密接口”反而容易被追问到失手。市面上还有 Playwright、Pyppeteer 这些后起之秀。它们确实有更稳的等待机制和更好的浏览器上下文管理但生态相对新参考资料没那么多。1688 这种老牌电商页面改动频繁Selenium 社区沉淀下来的答案足够多出问题时搜得到历史记录。别把爬虫做成一场选型秀哪个能稳定取到数据就用哪个。方案渲染完整度调试难度登录态维护毕设答辩友好度requests BeautifulSoup低动态字段拿不到需要抓包和分析接口需要手动处理加密参数一般Selenium高完整 DOM 可读低浏览器可视化调试cookie/user-data-dir 都可用高Playwright高中上下文管理更强中老师可能不熟悉2.2 解压后应该有哪些文件这份资源压缩包里标注了“含项目报告”所以解压出来不只有孤零零的脚本还会有一份能让毕设目录丰满起来的报告文档。以我拆过类似工程的经验看代码区域至少分成这几个职责1688_crawler/ ├── config.py # URL、关键词、采集间隔、字段名等所有配置 ├── browser.py # WebDriver 初始化、headless 开关、窗口参数 ├── login.py # 首次登录、cookies/localStorage 保存加载 ├── crawler.py # 列表翻页入口负责驱动业务流程 ├── parser.py # 从页面 DOM 中提取字段的解析函数 ├── models.py # 数据模型与去重主键定义 ├── storage.py # CSV / SQLite 写入 ├── requirements.txt # selenium、pandas 等依赖 ├── logs/ # 运行日志目录 └── report/ # 项目报告Word/PDF 形式config.py 单独存在的原因很简单1688 的页面结构和搜索词不应该散落在代码里。换一个关键词、换一套采集间隔改配置就够不用重新打包脚本。crawler.py 只做流程控制parser.py 只做页面解析存储单独放 storage.py某个环节挂了不会牵动全局。如果你手头的解压结构和上面不完全相等也不必纠结关键是看它有没有把登录、采集、解析、存储分开。如果所有逻辑挤在一个 main.py 里说明这份资源的工程组织比较弱改起来会很痛苦。项目报告在这个场景里不是摆设。毕设评委通常会看三样东西需求分析有没有说清数据来源、技术选型有没有理由、测试结果有没有数据支撑。报告里如果写了采集字段有哪几个、翻页策略怎么设计、反爬遇到什么现象那答辩时你就可以照着讲不用临时编。2.3 版本与兼容性Python、浏览器驱动、Selenium 怎么配这套工程对 Python 版本要求不算苛刻3.9 或 3.10 都可以装好依赖就能跑。selenium 4.x 之后很多老代码里常见的 find_element_by_xpath 已经被移到了 By 枚举里第一次运行如果报找不到 find_element_by_id往往是版本 API 差异而不是项目写错。还有一个很容易踩的坑是浏览器驱动版本。Chrome 和 Selenium 通过 chromedriver 通信而 chromedriver 有严格的版本对应关系。现在很多环境里已经内置了 Selenium Manager会在启动时自动帮你找 driver但内网环境或浏览器版本过新时仍会失效。稳妥的做法是去 driver 仓库下载与本地 chrome.exe 版本一致的 chromedriver放到 PATH 里或者直接在 browser.py 中指定路径from selenium import webdriver from selenium.webdriver.chrome.service import Service service Service(rD:\tools\chromedriver.exe) options webdriver.ChromeOptions() options.add_argument(--window-size1920,1080) driver webdriver.Chrome(serviceservice, optionsoptions)这段代码里 service 指定的是本地 driver 路径window-size 设成 1920x1080 是为了让列表页完整渲染。很多商品卡片是响应式布局窗口太窄会触发懒加载拉不出完整字段。有人习惯加上 --headless 隐藏浏览器窗口但如果第一次跑还没摸清页面结构建议先去掉这个参数亲眼看一次滚动、翻页、弹登录框的过程确认所有断点都稳定了再开 headless。依赖安装也尽量用固定约束文件不要图方便一条条手工 pip install。requirements.txt 里至少应该包含 selenium 和 pandas如果数据量不大pandas 也可以省略直接用 csv 标准库。核心是版本锁住换台电脑跑起来才不会被新版 API 坑到。2.4 把 70% 的变量收进 config.py我拆过很多爬虫资源好代码和烂代码最明显的分界线就是业务参数有没有外置到配置里。推荐一份简洁的 config.py# config.py # 采集目标配置 BASE_URL https://s.1688.com/selloffer/offer_search.htm KEYWORD 蓝牙耳机 MAX_PAGES 3 # 采集节奏配置单位秒 WAIT_SECONDS 2 PAGE_SCROLL_WAIT 2 DETAIL_WAIT 3 # 输出配置 OUTPUT_DB products.db CSV_PATH products.csv LOG_LEVEL INFOMAX_PAGES 是页数上限防止某个关键词搜索结果太多晚上挂机时跑不到头。WAIT_SECONDS 控制每个翻页动作之间的间隔DETAIL_WAIT 是详情页加载等待基线值。把节奏单独拎出来之后你后续调整采集频率只需要改 config不用进代码里翻 sleep。这也是项目报告里能写清楚的“参数化设计”。3. 登录态复用把“保持登录”做成可复用模块3.1 首次手动登录与用户数据目录1688 的商品详情页不登录状态下经常只能看到价格区间登录后才能看到区间底部甚至具体成交价。所以一套正经的采集流程绕不开登录态这件事。常见做法是第一次用 Selenium 打开登录页由人工输入账号密码并完成滑块验证脚本在登录成功后把 cookie 和存储数据保存到本地之后所有采集都复用这套登录态尽量不重复触发登录。这个阶段最容易犯的错是登录完直接关浏览器等下次启动发现登录状态全没了。原因是默认的 Chrome 临时用户目录在关闭后被清空。Selenium 里可以通过 user-data-dir 固定浏览器用户目录让浏览器把登录状态当作普通用户一样持久化options.add_argument(--user-data-dirC:/selenium_profile)我一般会用独立目录来跑爬虫不要和日常浏览器混在一起不然 cookie 会被各种插件干扰。这个目录保存了 Chromium 的 Local Storage、Session Storage、Cookie只要不删除下次启动就能免登录。不过 user-data-dir 有一个副作用同一个目录不能被两个 Chrome 进程同时使用。如果你开了多个采集任务它们会争抢这个目录出现登录态互相踢掉的现象。一个任务一个 profile 目录不要共用。3.2 Cookies 保存与加载的完整代码比 user-data-dir 更可控的方案是把登录后的 cookies 导出成一个 JSON 文件下次启动时加载。这种方式对毕设演示更友好因为你能明确看到 cookie 是哪个节点生成的报告里也能写清楚。# login.py import json from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By COOKIE_FILE cookies.json def save_cookies(driver): with open(COOKIE_FILE, w, encodingutf-8) as f: json.dump(driver.get_cookies(), f, ensure_asciiFalse, indent2) print(cookies saved:, COOKIE_FILE) def load_cookies(driver, login_url): driver.get(login_url) try: with open(COOKIE_FILE, r, encodingutf-8) as f: cookies json.load(f) except FileNotFoundError: return False driver.delete_all_cookies() for cookie in cookies: if expiry in cookie: cookie[expiry] int(cookie[expiry]) # 必须转 int driver.add_cookie(cookie) driver.refresh() return True def is_login(driver): try: WebDriverWait(driver, 5).until( EC.presence_of_element_located((By.CSS_SELECTOR, .mt-avatar-img)) ) return True except Exception: return False逻辑拆成三步先打开一个同域名的页面再逐条 add_cookie最后强刷页面。为什么必须先访问一次再添加 cookie因为 Selenium 的 add_cookie 要求驱动当前已经处于目标域名下否则会被浏览器拒绝。保存时不用做任何清理直接 dump 就行加载时 delete_all_cookies 则是为了防止旧 cookie 与文件里的新 cookie 冲突。expiry 字段如果不转 int部分版本的 Selenium 会报 TypeError这个坑很常见代码里特意加了一行注释。is_login 函数里的选择器要按自己页面改我写的是登录后导航栏的头像元素。你也可以改成判断 URL 里有没有某个登录跳转参数。关键是不能用 time.sleep 代替显式等待否则网络稍慢就会把已登录误判成未登录。3.3 别忘了 localStorage 里的 token纯 cookie 不一定够用。1688 这类站点偶尔会把 mtop token、用户信息放在 localStorage 或 sessionStorage 里只导出 cookie 下次还是会触发重新登录。解决方法是把关键存储也一并导出。def save_storage(driver): data { localStorage: driver.execute_script( return Object.entries(localStorage).reduce((acc, [k,v]) {acc[k]v; return acc;}, {}) ), sessionStorage: driver.execute_script( return Object.entries(sessionStorage).reduce((acc, [k,v]) {acc[k]v; return acc;}, {}) ), } with open(storage.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)加载 LocalStorage 时不能直接 set 一个 Object要写一个循环逐条设置def load_storage(driver): try: with open(storage.json, r, encodingutf-8) as f: data json.load(f) except FileNotFoundError: return for key, value in data.get(localStorage, {}).items(): driver.execute_script(localStorage.setItem(arguments[0], arguments[1]), key, value)注意 execute_script 返回值必须能被 JSON 序列化object 类型直接 return 会变成空对象所以上面用了 reduce 拼成普通字典。加载前同样要先打开目标域名否则 localStorage 没有可写入的 origin 环境。这段逻辑放好后首登一次后面跑十次都不用再输账号密码。3.4 登录失效后的自愈顺序登录态失效不是一定是 cookie 被清空了也可能是页面改版后头像选择器失效。自愈逻辑不要做成死循环。我一般这样安排进入采集入口前先调 is_login如果未登录尝试 load_cookies 后再判断一次如果还是失败保存当前页面截图然后停下来等人来处理。这个顺序保证脚本不会在登录失效时反复刷新页面把数据越跑越乱。def ensure_login(driver, login_url): if is_login(driver): return True load_cookies(driver, login_url) if is_login(driver): return True driver.save_screenshot(login_failed.png) raise RuntimeError(登录态失效请手动登录后重新运行)这里把登录态当成一个前置条件而不是在每个函数里重复判断。代码里截图文件名带上时间戳更好方便回看当时页面是什么状态。raise RuntimeError 是让主流程立刻中断日志里也能看到明确原因避免出现“跑了半小时发现全程没登录”的惨案。4. 商品信息采集实战列表翻页、详情页解析与数据落库4.1 列表页翻页与滚动先让页面把数据渲染完整个采集流程可以拆成三层列表层、详情层、存储层。列表层负责拿到商品 URL 集合详情层负责补充价格、主图、SKU 等信息存储层负责去重和落地。三层分开写每一层都能单独测试。打开 1688 搜索结果页后页面逻辑通常是滚动到底部加载更多部分页面才是按钮翻页。先判断列表容器是否出现再开始处理比一上来读 HTML 稳妥得多。下面这段是典型的滚动加载实现import time def scroll_to_bottom(driver, max_scroll10): last_height driver.execute_script(return document.body.scrollHeight) for _ in range(max_scroll): driver.execute_script(window.scrollTo(0, document.body.scrollHeight)) time.sleep(2) new_height driver.execute_script(return document.body.scrollHeight) if new_height last_height: break last_height new_height return last_height滚动加载不能只用一次 scrollTo因为 1688 详情页普遍做了图片懒加载图片没加载完时页面高度不会立刻增长。代码里设置 max_scroll 是为了防止某个异常页面无限滚下去这是所有死循环类 bug 的后备保险。每滚一次睡 2 秒看起来慢但能显著降低被反爬盯上的概率。翻页按钮的点击要区分“下一页”和“第 N 页”。很多实现里写“点击下一页”会导致采集路径乱掉特别是最后一页时按钮会变成 disabled。正确做法是优先记录当前页码点击后等待页码文本变化再解析下一页的商品条目from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By def click_next_page(driver): current driver.find_element(By.CSS_SELECTOR, .pagination .active).text driver.find_element(By.CSS_SELECTOR, .next-page-btn).click() WebDriverWait(driver, 10).until( EC.text_to_be_present_in_element( (By.CSS_SELECTOR, .pagination .active), str(int(current) 1) ) )text_to_be_present_in_element 是等待翻页真正完成的推荐方式比 sleep 更精确。如果找不到 active 元素说明当前页面没有翻页按钮那它多半是无限滚动模式应该回到 scroll_to_bottom 的逻辑里处理。真正写爬虫时最好把列表页截图和当前 DOM 摘要一起保存方便排查翻页卡在哪一步。4.2 详情页解析字段提取与去重列表页能拿到商品标题、主图和链接但价格、销量、SKU 这些字段经常要进详情页才有。采集策略一般分两步先抓列表页的原始链接集合再批量进入详情页解析。def extract_list_items(driver): items [] cards driver.find_elements(By.CSS_SELECTOR, .offer-card) for card in cards: title_el card.find_elements(By.CSS_SELECTOR, .title) link_el card.find_elements(By.CSS_SELECTOR, a.title) price_text card.find_elements(By.CSS_SELECTOR, .price) items.append({ title: title_el[0].text.strip() if title_el else , url: link_el[0].get_attribute(href) if link_el else , price: price_text[0].text.strip() if price_text else , source_page: driver.current_url, }) return items这段代码里的选择器是示例真实项目里一定是从页面复制结构之后再替换。这里的重点是两个去重点同一轮翻页里列表页可能出现相同的推荐商品不同轮次之间重复链接也可能出现。所以在 storage 层必须有一个按 url 去重的逻辑否则后面统计时同一个商品会被当成多个新品毕设的结论直接失真。进入详情页后解析字段要比列表页谨慎。价格标签可能在多个元素里显示“¥0.01”的活动价会干扰真实成交价判断。建议只取详情页主价格容器下的文本而不是全页面第一条带 “¥” 的字符串。标题字段有时会拆成多个 span需要拼接之后再 strip 空白。写 XPath 时text() 只能匹配当前节点的直接文本子节点里的文字抓不到要取全量文本时用 string(.)。from selenium.webdriver.common.by import By def parse_detail(driver, url): driver.get(url) time.sleep(3) title driver.execute_script(return document.querySelector(.title-text).innerText) raw_price driver.execute_script(return document.querySelector(.price-original)? document.querySelector(.price-original).innerText : ) main_image driver.execute_script(return document.querySelector(.main-image img)? document.querySelector(.main-image img).src : ) return { url: url, title: title.strip(), price: raw_price.strip(), main_image: main_image, }execute_script 取值有一个好处innerText 会把隐藏节点过滤掉拿到的是用户真正能看到的文本。如果某次运行发现返回的 title 为空大概率是页面弹了登录遮罩把元素挡掉了这时候第一反应应该是回到登录态判断逻辑而不是改选择器。4.3 数据落库用 SQLite 还是 CSV毕设级数据量通常没有大到需要 MySQLSQLite 是最合适的中间层。它不需要单独起服务一个文件即数据库支持 SQL 去重也能随时导出成 Excel 看结果。如果导师只要求看到抓取结果CSV 也够用但 CSV 的乱码和重复问题会让你反复返工。SQLite 建表时建议这样设计主键CREATE TABLE product ( url TEXT PRIMARY KEY, title TEXT, price TEXT, main_image TEXT, sku_count INTEGER DEFAULT 0, crawled_at DATETIME DEFAULT CURRENT_TIMESTAMP );用 url 当主键天然完成去重。写入时采用 INSERT OR REPLACE这样同一商品再次被抓到时只更新价格和抓取时间不会新增一行。SKU 数量这类嵌套字段如果解析不到就按默认 0 处理不要写在同一个表里动态加列否则后面统计会乱。抓完一批数据后我习惯立刻跑一句汇总 SQL确认最终表里有多少个商品链接。如果结果明显少于预期先看日志八成是列表翻页在某一步停止了而不是详情页解析的问题。5. 避坑指南登录态过期、滑块验证、元素超时与日志排查5.1 登录态频繁过期现象前一天还能直接跑第二天一启动就跳回登录页保存好的 cookie 像失效了一样。原因cookie 本身带过期时间但更常见的是采集频率太高账号被判定为异常访问后强制登出。另一个隐蔽原因是 user-data-dir 被多个 Chrome 进程同时占用启动时加载到了残缺状态。解决先把采集间隔从 0.5 秒调到 3 秒以上观察是否稳定。再检查 browser.py 里 user-data-dir 是否唯一不要多个采集进程共用同一个目录。如果登录态还是掉就手动重新登录一次并覆盖保存 cookie不要保留两份 cookie 文件。这里还有一个细节cookie 文件保存时按域名做了隔离如果你的代码里打开了一个 1688 之外的页面再添加 cookie 会失败所以加载前要确保浏览器先导航到目标站点的主域名。5.2 滑块验证码反复弹出现象页面一打开滑块弹窗就顶在中间下面的商品元素怎么 wait 都找不到。原因短时间高频切换页面风控认为当前浏览器环境异常于是要求人机验证。这个不是网络 IP 问题单纯换 IP 没用1688 对浏览器行为指纹的敏感度比 IP 变化更高。解决不要做任何自动绕过滑块的动作毕设场景也没必要。先把采集间隔加大headless 模式关掉让浏览器以真实窗口模式运行。如果滑块仍然出现就干脆在这个节点停下来由人工拖动一次脚本在验证通过后再继续。注意验证码通过的页面不要急着立刻翻页让页面稳定几秒再执行下一步采集。5.3 页面明明加载了但元素找不到现象打开商品列表页后截图能看到整块商品卡片但 find_elements 返回空列表或者报 no such element。原因页面内容在 iframe 里Driver 默认上下文拿不到内层 DOM。或者商品卡片是懒加载区域可见区里没有它浏览器还没渲染出来。还有可能是页面弹窗遮罩挡住元素确实存在但不可交互。解决先执行 driver.switch_to.frame 切换进 iframe再重试定位。懒加载问题则先把滚动逻辑跑完再用 WebDriverWait 等一个具体选择器出现。代码里建议把选择器和等待条件放到函数前面调试时用 driver.page_source 搜索某个关键词确认到底是不是 iframe 问题。5.4 采集速度稍快就开始出现“访问频繁”现象连续翻到第三、四页页面里商品全部消失替换成一个提醒访问频繁的空白页。原因翻页间隔太短点击逻辑里没有等待渲染完成就连续点了下一页。后端虽然没封 IP但前端做了行为频率约束你动作太快和正常用户差得太远。解决把点击翻页后的等待从 time.sleep(2) 改成显式等待页码变化。同时记录一个最小间隔配置每个商品详情页访问之间休息 1 到 2 秒。还有一个土办法但很有效每次翻页后随机滚动一段距离不要太规律。把页面动作尽量做得像人而不是像循环。5.5 CSV 打开乱码或重复数据现象用 Excel 打开生成的 CSV中文全是乱码同一商品在表格里出现多行。原因CSV 默认编码是 UTF-8Excel 打开时按 ANSI 解码所以乱码。重复数据则是因为没有按 url 去重或者详情页解析过程中断了重跑同一批链接被再次抓取。解决写入 CSV 时指定 encodingutf-8-sigExcel 就能正确识别。重复问题靠 SQLite 的唯一主键兜底导出时增加查询 SELECT COUNT(*) FROM product如果行数和预期不一致就去看抓取日志里的失败页数量。每天跑数据之前先把上次的数据库做一次备份别问我是怎么知道的。6. 验证与进阶用日志和抽样断言把采集结果跑通6.1 日志分级我不会把 print 当日志用。采集脚本一跑就是几百页print 的内容会淹没在滚动输出里。项目里最好按级别拆分info 记录翻到第几页、采集到第几个商品warning 记录元素缺失但还能继续跑的页error 记录登录失效、页面跳转异常这类必须停下来的故障。Python 自带的 logging 足够不用为了毕设引入重量级框架。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, filenamelogs/crawler.log, )6.2 抽样断言跑完一批数据后我会手动抽查 5 个详情链接确认标题和价格不是空的。再用一个断言脚本对全表做检查字段为空或重复超预期的直接报错。这套验证逻辑可以作为项目报告里的“结果验证”章节素材。import sqlite3 conn sqlite3.connect(products.db) rows conn.execute(SELECT url, title, price FROM product).fetchall() assert len(rows) 0 assert all(r[1] and r[2] for r in rows), 存在空标题或空价格 assert len(set(r[0] for r in rows)) len(rows), url 重复从那以后我每次跑采集前都会把登录态判断、元素等待和抽样断言强制过一遍先跑 3 页确认数据字段正常再全量跑数据最后收尾做一次重复检查。这个习惯救过我很多次希望帮到你。本文还有配套的精品资源点击获取