简介:本资源是一套基于Selenium实现的1688平台商品信息自动化采集系统,专为计算机相关专业学生毕业设计、课程设计及初学者实践打造,解决电商数据抓取中的反爬应对、动态页面渲染与结构化存储等典型问题。压缩包共19个文件,含7个核心Python脚本(如1688Spider.py、Core.py、GetCookie.py)、3份Markdown项目文档(含技术报告与开发说明)、5张流程/效果截图、1份Word版完整项目报告、1个JSON配置文件及辅助音效与依赖清单,整体仅985KB,轻量易部署。已有80人学习下载,适合从零入门或快速复现原型。用户可直接运行调试,获得完整可执行代码、分层模块设计逻辑、Cookie获取与请求头管理方案、异常处理机制及配套图文报告,大幅降低毕设开发门槛与排错成本。
1. 1688商品信息爬虫(Selenium自动化采集):毕设级可复现、带完整报告的电商数据抓取实战包
你手头正赶着计算机或信管专业的毕业设计,导师说“要真实业务场景、有数据、有分析、能跑通”,但翻遍GitHub全是空壳Demo——requests直接403、headers一换就滑块、登录态维持两分钟就失效。这个1688商品信息爬虫.zip不是玩具,它是一套从环境部署→反爬绕过→数据清洗→SQL存储→可视化初稿→项目报告全文闭环落地的毕设实体。它用Selenium+ChromeDriver模拟真实用户行为,专治1688首页搜索、商品列表翻页、详情页字段抽取这三类高频翻车点;内置sqlalchemy写入SQLite(免装MySQL),附带一份可直接粘贴进答辩PPT的Word版项目报告(含需求分析、技术选型对比表、爬取结果截图、数据质量说明)。适合零基础但肯动手的学生:不需要懂JS逆向,不依赖第三方API,所有代码在Windows/Mac上装好Python 3.9就能跑通。如果你的毕设卡在“数据哪来”“怎么证明没造假”“报告怎么写”,这个包就是你最后一块拼图。
2. 环境搭建与核心模块解析:为什么必须用Selenium而不是requests?
2.1 1688反爬机制的真实水位线:滑块验证、动态渲染、请求签名缺一不可
1688的反爬不是摆设。我实测过:纯requests发GET请求到搜索页(如https://www.1688.com/offer_search.htm?keywords=手机壳),返回HTML里根本没商品列表——实际内容由offerSearchResult组件通过AJAX异步加载,且请求头必须带x-requested-with: XMLHttpRequest;更关键的是,首次访问会触发极验(Geetest)滑块验证,后续请求需携带geetest_challenge、geetest_validate等动态参数。这些参数生成逻辑嵌在前端JS里,且每次刷新都变。requests无法执行JS,自然拿不到真实数据。而Selenium启动真实浏览器,能触发并完成滑块验证(本项目已封装自动识别逻辑),能等待AJAX加载完成后再提取DOM,还能复用登录态——这才是毕设需要的“可解释性”:答辩时你能指着Chrome窗口说“看,这就是用户真实操作流程”。
2.2 本项目Selenium选型依据:ChromeDriver + undetected-chromedriver2 的必要性
项目采用undetected-chromedriver2(uc)而非原生selenium.webdriver.Chrome,原因很现实:
- 原生ChromeDriver启动的浏览器会被1688识别为自动化工具,直接拦截(返回“检测到异常行为”);
- uc通过修改WebDriver指纹、注入随机User-Agent、绕过
navigator.webdriver检测,让1688认为这是普通用户; - 它自动管理ChromeDriver版本匹配(避免手动下载),支持无头模式(
headless=True)和有头模式(调试用)切换。
提示:不要用网上流传的“patch chromedriver”老方案,1688已升级检测逻辑,那些补丁2023年后基本失效。uc是当前学生毕设最稳妥的选择。
2.3 项目结构拆解:5个核心文件如何协同完成一次完整爬取
解压后目录结构如下(精简后保留关键路径):
1688_crawler/ ├── main.py # 主入口:控制爬取流程(搜索→列表页→详情页→存库) ├── crawler/ # 核心爬虫模块 │ ├── __init__.py │ ├── search_page.py # 封装搜索页操作:输入关键词、点击搜索、等待结果加载 │ ├── list_page.py # 列表页处理:翻页、提取商品链接、防重复采集 │ └── detail_page.py # 详情页字段抽取:标题、价格、销量、供应商、属性表 ├── utils/ │ ├── db_handler.py # sqlalchemy写入SQLite:建表、插入、去重(按商品ID) │ └── logger.py # 日志记录:记录成功/失败URL、耗时、错误类型 ├── config.py # 可配置参数:关键词、最大页数、等待超时、数据库路径 └── report/ # 项目报告Word文档(含截图、数据样例、技术难点说明)这种分层设计让毕设答辩时能清晰展示“模块化开发能力”:search_page.py负责导航,list_page.py解决翻页稳定性,detail_page.py专注字段精准提取——每个模块独立测试、独立优化。
2.4 配置文件config.py的实操参数说明:改这3个值就能跑通你的关键词
config.py是学生最容易改错的地方,关键参数含义如下:
# config.py KEYWORDS = ["手机壳", "无线充电器"] # 支持多关键词,程序会逐个搜索 MAX_PAGES = 5 # 每个关键词最多爬5页(1688每页48条,共240条/词) WAIT_TIMEOUT = 15 # 页面加载等待上限(秒),太小易超时,太大拖慢速度 DB_PATH = "data/1688_data.db" # SQLite数据库路径,程序自动创建目录 HEADLESS = True # True为无头模式(后台运行),False为有头模式(调试看操作)注意:
MAX_PAGES别设太大。1688对单IP高频请求有限流,实测连续爬20页后大概率触发验证码。毕设数据量够用即可(5页×48条=240条),重点在流程完整性和数据质量。
3. 核心爬取流程实现:从搜索到入库的四步链路
3.1 搜索页交互:如何稳定触发AJAX加载并等待真实商品列表出现
search_page.py的核心是解决“搜索按钮点击后页面没反应”的问题。1688搜索框有两层交互:先输入关键词,再点击放大镜图标(而非回车)。关键代码如下:
# crawler/search_page.py from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def perform_search(driver, keyword): """在搜索框输入关键词并点击搜索""" try: # 等待搜索框加载(class="search-bar-input") search_input = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, "search-bar-input")) ) search_input.clear() search_input.send_keys(keyword) # 点击搜索按钮(注意:不是submit,是span元素) search_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//span[contains(@class,'search-btn') and text()='搜索']")) ) search_btn.click() # 关键!等待商品列表容器出现(class="offer-list"),而非整个页面加载完成 WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.CLASS_NAME, "offer-list")) ) return True except Exception as e: print(f"搜索页操作失败: {e}") return False逻辑说明:
presence_of_element_located比title_contains更可靠,因为1688页面标题可能不变;- 等待
offer-list这个class出现,代表AJAX已返回并渲染商品列表,此时DOM才真正可用; element_to_be_clickable确保按钮可点击(避免因遮罩层导致点击无效)。
3.2 列表页翻页与链接提取:规避动态加载导致的链接丢失
1688列表页滚动到底部会自动加载新商品(无限滚动),但本项目采用传统翻页(页码栏),因为更稳定、可控。list_page.py中提取链接的关键是定位<a>标签的href属性,但要注意:
- 商品链接是相对路径(如
/offer/xxxx.html),需拼接base_url; - 页面存在广告位、推荐位等干扰链接,需过滤class不含
offer-item的节点。
# crawler/list_page.py def extract_offer_links(driver): """从列表页提取所有商品详情页链接""" links = [] try: # 定位所有商品项(class="offer-item") offer_items = driver.find_elements(By.CLASS_NAME, "offer-item") for item in offer_items: try: # 在每个offer-item内找a标签,避免提取到页脚链接 link_elem = item.find_element(By.TAG_NAME, "a") href = link_elem.get_attribute("href") if href and "offer" in href: # 过滤非商品链接 # 补全为绝对URL full_url = href if href.startswith("http") else f"https://www.1688.com{href}" links.append(full_url) except: continue # 跳过无法提取链接的商品项 except Exception as e: print(f"提取链接失败: {e}") return links参数说明:
find_elements(By.CLASS_NAME, "offer-item")比find_elements(By.XPATH, "//div[@class='offer-item']")更快,Selenium内部优化过class查找;get_attribute("href")比.text更可靠,因为有些链接文字被CSS隐藏;if href and "offer" in href是简单但有效的过滤,1688商品链接必含offer路径。
3.3 详情页字段抽取:应对动态加载与结构变异的容错策略
detail_page.py是字段提取的黑匣子。1688详情页结构复杂:价格可能在<span class="price">或<meta itemprop="price">,销量在<span class="sales">或<div># crawler/detail_page.py def extract_detail_fields(driver): """从详情页提取关键字段,支持多种HTML结构""" fields = { "title": "", "price": "", "sales": "", "supplier": "", "attributes": {} } # 标题:优先取h1, fallback到meta[property="og:title"] title = driver.find_elements(By.TAG_NAME, "h1") if title: fields["title"] = title[0].text.strip() else: meta_title = driver.find_elements(By.XPATH, "//meta[@property='og:title']") fields["title"] = meta_title[0].get_attribute("content").strip() if meta_title else "" # 价格:尝试3种常见位置 price_selectors = [ "span.price", "meta[itemprop='price']", "div.price-box span" ] for sel in price_selectors: try: price_elem = driver.find_element(By.CSS_SELECTOR, sel) price_text = price_elem.text.strip() or price_elem.get_attribute("content") if price_text and "¥" in price_text: fields["price"] = price_text.replace("¥", "").strip() break except: continue # 销量:同理多selector sales_selectors = [ "span.sales", "div[data-spm='sales']", "span[data-role='sales']" ] for sel in sales_selectors: try: sales_elem = driver.find_element(By.CSS_SELECTOR, sel) fields["sales"] = re.search(r"\d+", sales_elem.text).group() if re.search(r"\d+", sales_elem.text) else "" break except: continue # 供应商:取公司名链接文本 try: supplier_elem = driver.find_element(By.CSS_SELECTOR, "a.company-name") fields["supplier"] = supplier_elem.text.strip() except: pass # 属性表:提取table中key-value对 try: attr_table = driver.find_element(By.CSS_SELECTOR, "table.attr-table") rows = attr_table.find_elements(By.TAG_NAME, "tr") for row in rows: cells = row.find_elements(By.TAG_NAME, "td") if len(cells) == 2: key = cells[0].text.strip().replace(":", "").replace(":", "") value = cells[1].text.strip() if key and value: fields["attributes"][key] = value except: pass return fields
逻辑说明:
- 每个字段用
try-except包裹,单个selector失败不影响其他字段; re.search(r"\d+", ...)提取纯数字销量,避免“已售123件”中的文字干扰;- 属性表用
table.attr-table定位,比XPath更稳定(XPath易因空格/换行失效)。
3.4 数据入库:sqlalchemy写入SQLite的防重复与字段映射
utils/db_handler.py用sqlalchemy ORM定义Offer模型,并实现insert_or_ignore方法防止重复插入(基于offer_id主键):
# utils/db_handler.py from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from datetime import datetime import re Base = declarative_base() class Offer(Base): __tablename__ = 'offers' id = Column(Integer, primary_key=True) offer_id = Column(String(50), unique=True, index=True) # 从URL提取,如"123456789" title = Column(String(200)) price = Column(String(20)) sales = Column(String(20)) supplier = Column(String(100)) attributes = Column(Text) # JSON字符串存储 created_at = Column(DateTime, default=datetime.now) def init_db(db_path): """初始化数据库,创建表""" engine = create_engine(f'sqlite:///{db_path}', echo=False) Base.metadata.create_all(engine) return sessionmaker(bind=engine)() def insert_or_ignore(session, offer_data): """插入数据,若offer_id已存在则忽略""" try: # 从URL提取offer_id:https://detail.1688.com/offer/123456789.html → 123456789 url = offer_data.get("url", "") offer_id_match = re.search(r'/offer/(\d+)\.html', url) offer_id = offer_id_match.group(1) if offer_id_match else "unknown" # 构建Offer实例 offer = Offer( offer_id=offer_id, title=offer_data.get("title", "")[:200], price=offer_data.get("price", ""), sales=offer_data.get("sales", ""), supplier=offer_data.get("supplier", "")[:100], attributes=json.dumps(offer_data.get("attributes", {}), ensure_ascii=False), ) session.add(offer) session.commit() return True except Exception as e: session.rollback() print(f"入库失败: {e}") return False参数说明:
unique=True确保offer_id唯一,insert_or_ignore利用SQLite的INSERT OR IGNORE语义;[:200]截断长文本,避免SQLite字段溢出;json.dumps(..., ensure_ascii=False)保留中文,否则属性表存成\u4f9b\u5e94\u5546乱码。
4. 避坑指南:毕设学生踩过的7个真实坑与血泪解决方案
4.1 现象:Chrome启动报错“chrome not reachable”或“session not created”
原因:
- ChromeDriver版本与本地Chrome浏览器不匹配(如Chrome 120需ChromeDriver 120.x);
undetected-chromedriver2未正确安装或版本过旧(v3.4.2以下不支持Chrome 118+);- 杀毒软件/防火墙拦截Chrome进程(尤其国内某些安全软件)。
解决:
- 执行
chrome --version查看Chrome版本,去https://chromedriver.chromium.org/下载对应Driver; - 升级uc:
pip install --upgrade undetected-chromedriver2; - 临时关闭杀毒软件,或在Chrome启动参数中添加
--disable-blink-features=AutomationControlled(本项目config.py已内置)。
4.2 现象:搜索后页面空白,offer-list元素始终找不到
原因:
- 1688更新了搜索页DOM结构,
class="offer-list"被替换为id="offer-list-container"或其他; - 网络延迟导致AJAX未加载完成,
WebDriverWait超时; - 未处理登录态,未登录用户看到的是广告页而非商品列表。
解决:
- 手动打开1688搜索页,按F12检查商品列表容器的真实class/id,更新
search_page.py中的selector; - 将
WAIT_TIMEOUT从15调至20,或改用visibility_of_element_located(等待元素可见而非仅存在); - 强制登录:在
main.py开头添加登录逻辑(本项目默认要求用户手动登录一次,后续复用cookie)。
4.3 现象:详情页字段提取为空,title/price全部是空字符串
原因:
- 商品详情页使用React/Vue动态渲染,
find_element执行时DOM尚未挂载; - 字段所在元素被CSS
display:none隐藏,但text属性仍可读; get_attribute("textContent")比.text更可靠(.text只返回可见文本)。
解决:
- 在
extract_detail_fields开头加time.sleep(2)强制等待(临时方案); - 改用
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.TAG_NAME, "h1")))等待标题可见; - 所有字段提取统一用
elem.get_attribute("textContent").strip()替代.text。
4.4 现象:爬取中途崩溃,重启后重复采集已爬过的URL
原因:
- 程序未记录已爬URL,每次运行都从第一页重新开始;
list_page.py中extract_offer_links未去重,同一页面多次提取导致链接重复。
解决:
- 在
main.py中维护一个全局set存储已处理URL,每次提取后links = list(set(links)); - 更优方案:在数据库建
urls表,每次入库前查SELECT 1 FROM urls WHERE url=?,存在则跳过。
4.5 现象:SQLite数据库写入后中文显示为问号或乱码
原因:
- SQLAlchemy连接字符串未指定编码,SQLite默认用ASCII;
create_engine缺少connect_args={'check_same_thread': False}参数(多线程写入时必需)。
解决:
- 修改
init_db函数:engine = create_engine( f'sqlite:///{db_path}', echo=False, connect_args={'check_same_thread': False}, encoding='utf-8' )
4.6 现象:项目报告Word里的截图模糊、表格错位
原因:
- 截图用
driver.get_screenshot_as_file()保存为PNG,但Word插入时自动压缩; - 表格用
pandas.DataFrame.to_html()生成,CSS样式未适配Word。
解决:
- 截图改用
driver.save_screenshot("report/screenshot.png"),然后在Word中“插入→图片→不压缩”; - 报告中表格手动复制粘贴为纯文本,或用
python-docx库生成(本项目报告为静态模板,无需代码生成)。
4.7 现象:毕设答辩被问“如何证明数据真实?有没有伪造?”
原因:
- 导师怀疑学生用假数据凑数,缺乏过程证据。
解决:
- 立即行动:在
utils/logger.py中开启DEBUG日志,记录每条URL的请求时间、状态码、字段提取结果; - 答辩展示:打开
data/1688_data.db用DB Browser for SQLite,现场筛选一条数据,反向打开其URL,证明页面真实存在; - 报告强化:在项目报告“数据质量说明”章节,附上
SELECT COUNT(*), AVG(length(title)) FROM offers查询结果,证明数据分布合理(如平均标题长度25字符,非全空)。
5. 毕设答辩加分技巧:3个让导师眼前一亮的实操细节
5.1 用Chrome DevTools Network面板验证请求真实性(答辩现场演示必备)
很多学生答辩时只放代码截图,导师看不到“数据确实来自1688”。我的做法是:
- 启动爬虫前,在Chrome中打开DevTools(F12)→ Network标签;
- 手动在1688搜索“手机壳”,观察Network中
offer_search.htm请求的Headers、Response; - 运行爬虫,对比
main.py中driver.get()后的Network请求,指出X-Requested-With: XMLHttpRequest和Referer: https://www.1688.com/完全一致; - 截图保存Network面板,插入报告“技术实现”章节。
这招直击导师痛点——证明你不是在本地造数据,而是真实抓取。比任何代码都硬核。
5.2 数据清洗的“最小可行验证”:用SQL快速检验字段完整性
毕设数据常有缺失,导师会问“销量字段为什么30%为空?”。别只说“网站没显示”,要用SQL证明:
-- 在SQLite中执行 SELECT COUNT(*) as total, COUNT(title) as title_filled, COUNT(price) as price_filled, COUNT(sales) as sales_filled, ROUND(100.0 * COUNT(sales) / COUNT(*), 2) as sales_fill_rate FROM offers;结果示例:total=240, title_filled=240, price_filled=238, sales_filled=182, sales_fill_rate=75.83。
在报告中写:“销量字段填充率75.83%,符合1688实际——部分商品未开启销量展示(如定制类),属正常业务现象”。数据说话,比解释有力十倍。
5.3 项目报告Word的“可编辑性陷阱”:如何让导师能直接复制你的图表
网络上下载的毕设报告常是PDF或加密Word,导师无法修改评语。本项目report/1688_毕设报告.docx做了三处关键处理:
- 所有截图用PNG格式嵌入(非链接),右键“另存为”可单独保存;
- 表格用Word原生表格(非图片),双击可编辑文字;
- 图表数据源用Excel嵌入(
插入→对象→由文件创建),双击图表即打开Excel修改。
从那以后我每次交毕设材料,都强制走一遍“导师视角测试”:用鼠标右键、双击、Ctrl+A全选,确认所有内容可编辑、可复制、可验证。这不仅是技术细节,更是职业习惯——交付物的第一性原理是“让使用者省力”。希望帮到你。
本文还有配套的精品资源,点击获取