ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python爬虫实战:批量采集1688商品数据的完整指南

Python爬虫实战:批量采集1688商品数据的完整指南 做电商选品或者市场调研的朋友应该都有过这种经历想在1688上找一批商品数据做分析比如对比同行价格、挖掘潜力爆款、整理供应商名录结果打开网页手动复制粘贴几百个商品折腾一晚上腰酸背痛不说数据还容易抄错。我自己当初做供应链调研的时候就干过这种傻事后来实在扛不住了才老老实实写脚本去批量采集阿里巴巴批发网商品数据。这篇文章不整虚的直接讲清楚用爬虫工具批量采集1688商品数据的完整思路和实操过程。里面会涉及请求构造、页面解析、数据清洗、反爬应对这些核心环节适合有三四个月Python基础、想拿真实项目练手的朋友也适合电商运营、数据分析岗的同学参考——哪怕你编程基础薄弱跟着步骤走也能跑通一个基础版本。我先把话放在前面所有操作必须建立在合法合规、尊重平台规则的前提下只采集公开可见的数据控制好请求频率别给平台添乱也别给自己惹麻烦。1. 采集方案设计动手之前先把这些事想清楚1.1 明确需求你要采什么字段、采多少数据很多人拿着爬虫教程就往1688上冲结果跑了一半发现字段不对、数据量不够、压根没法用就是因为动手前没把需求捋清楚。采集之前先问自己三个问题。第一采哪些字段我做过一个供应商对比项目需要的核心字段是商品标题、价格区间1688上很多商品是一口价和起批量组合价、起批量、累计销量、发货地、供应商名称、供应商所在地、商品链接。如果你要做竞品分析可能还要加SKU信息、店铺评分、回头率。这些字段直接决定了你后面解析逻辑怎么写。第二采多少条是只采搜索结果前两三页做参考还是要采几千条做趋势分析这决定了你用 requests 慢慢跑就行还是得上 Scrapy 这类异步框架。仅做小规模验证的话没必要一上来就上分布式采集把简单方案跑通再说。第三数据拿来干什么这是合规边界问题。用于个人学习、自有业务的数据分析问题不大如果拿去转卖或者大规模商用那就得谨慎评估法律风险。我的原则很简单只采公开数据、不采用户个人信息、不绕过登录权限、不给目标服务器造成压力。1.2 技术选型不同基础的人该用哪套方案1688不是那种纯静态的简单站点选择采集方案时不能只看“能不能跑通”还要看后续维护成本。我根据自己的实操经验把常见方案分为四档。第一档是现成采集工具比如后羿采集器、八爪鱼这类可视化软件。优点是零基础就能上手鼠标点几下就能配置采集规则缺点是灵活度差页面改版后规则经常失效而且免费版有采集条数限制。适合只想临时采一批数据、不想写代码的朋友。第二档是 Python requests BeautifulSoup 组合。这是最经典、也是最适合入门的方式。requests 负责请求网页BeautifulSoup 负责解析HTML代码量不大调试也方便。我在实际项目中小规模采集基本都用这套灵活性足够出了问题也容易排查。第三档是 Scrapy 框架。它的优势在于并发调度、去重、管道处理都内置了适合大规模采集。但学习曲线比 requests 组合陡不少而且对新手来说Scrapy 的调试流程相对复杂。我的建议是需求没到“每天采集上万条”这个量级别急着上 Scrapy。第四档是 Playwright / Selenium 这类浏览器自动化工具。如果目标页面数据是JS动态加载requests 直接拿不到那就用它们去模拟真实浏览器操作。1688 的部分页面信息就存在这种情况后面我会详细说。1.3 合规与风控的边界意识这部分必须单独拿出来讲因为它比技术本身更重要。我见过一些新手刚学会爬虫就到处乱采结果IP被封是小事严重的还会收到平台法务函。做采集一定要有边界意识。首先检查一下目标站点的 robots 协议这是最基本的礼貌。虽然 robots.txt 不直接具有法律强制力但它能反映站点的采集意愿。其次严格遵守平台用户协议别去采集需要登录才能看到的非公开数据更不要尝试绕过验证码、滑块这类反爬机制——那是明确的违规行为。最后控制请求频率我实操中一般把每次请求间隔设置在2到5秒并且加上随机抖动既能保证采集效率又不会给平台服务器造成负担。提示本文所有代码和技术讲解均以学习研究为目的。实际使用时请自行评估并严格遵守相关法律法规、平台服务条款及robots协议风险自负。2. 1688页面结构与数据定位核心细节2.1 搜索结果页还是商品详情页数据形态差很多刚开始做采集的人很容易忽略一个问题列表页和详情页的数据形态完全是两码事。搜索结果页比如搜索“保温杯”出来的商品列表能拿到的字段有限——商品标题、主图、价格区间、起批量、销量这些信息足够做初步筛选。但如果你需要供应商详细资料、完整SKU表、物流信息就必须进详情页。我当时的做法是“列表页 详情页”两级采集先抓搜索结果页拿到商品ID和基础字段再根据商品ID拼接详情页URL补全深层次信息。这样做的好处是请求次数可控不会对页面造成过大压力而且如果中途挂了只要记录好已采集的商品ID就能断点续跑。2.2 构造请求与参数解析1688搜索结果页的URL结构核心参数就是 keywords、startPage 和 sortType 这几个。比如keywords搜索关键词直接决定搜索结果startPage页码控制翻页sortType排序方式常见的有综合排序、销量排序、价格排序构造请求的时候headers 里必须带上 User-Agent、Referer、Accept 这几个字段。其中 User-Agent 一定要设置成真实浏览器的UA很多反爬检测首先看的就是这个。Referer 指向搜索页本身有时候漏掉Referer服务器会直接返回异常页面。Cookie 这块儿要注意未登录状态下能拿到的都是公开数据有些字段比如部分供应商联系方式需要登录才能看到那就属于非公开数据了我建议直接放弃不要尝试通过技术手段绕过登录态去获取。爬虫的边界就在这里过了线就不是技术问题而是法律问题了。2.3 页面解析策略优先找JSON其次是HTML最后才是正则现在很多页面数据是混在HTML里的但也有一些数据藏在window._data_这类全局变量里。我的解析顺序是这样的先看开发者工具里的 Network 面板如果发现页面数据是通过XHR接口加载的并且返回的是JSON格式那就直接用 requests 调那个接口解析效率最高代码也最简洁。如果数据已经渲染在HTML里那就用 BeautifulSoup 配合 CSS 选择器或 XPath 提取。正则表达式是兜底方案能不用就不用——它太脆弱页面结构稍微一变正则就废了。实操心得打开浏览器开发者工具F12切到 Network 面板刷新页面重点看 XHR 和 Fetch 类型的请求很多时候你会发现页面里的商品数据其实来自一个独立的JSON接口。找到这个接口你的代码会简洁一半。3. 完整实操流程从零实现一个1688商品采集脚本3.1 环境准备与依赖安装这一节内容比较细跟着做就行。# 建议使用 Python 3.9 pip install requests beautifulsoup4 lxml pandas openpyxl我常用到的库就这几个requests 负责发请求beautifulsoup4 负责解析HTMLlxml 是解析器比默认的 html.parser 快很多pandas 负责数据清洗和导出Excel。3.2 列表页采集拿商品基础信息先写一个最基础的搜索请求我直接贴关键代码。import requests from bs4 import BeautifulSoup import time import random HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://www.1688.com/ } def fetch_search_page(keyword, page): url https://s.1688.com/selloffer/offer_search.htm params { keywords: keyword, startPage: page, sortType: default, } resp requests.get(url, headersHEADERS, paramsparams, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding return resp.text这一步先别想着解析先把页面成功拿下来再说。我调试的时候习惯先把返回的HTML存到本地文件里用浏览器打开看看结构对不对。如果请求头没构造好返回的往往是一段提示异常跳转的脚本而不是商品列表。拿到HTML之后下一步是根据页面结构提取商品数据。1688列表页的商品块是有规律可循的我用的解析代码类似这样def parse_listing(html): soup BeautifulSoup(html, lxml) items [] # 根据实际页面结构调整选择器 for card in soup.select(.space-offer-card-box): try: title_elem card.select_one(.title, .offer-title) price_elem card.select_one(.price, .offer-price) sales_elem card.select_one(.sale-count, .offer-sale) link_elem card.select_one(a) item { title: title_elem.get_text(stripTrue) if title_elem else , price: price_elem.get_text(stripTrue) if price_elem else , sales: sales_elem.get_text(stripTrue) if sales_elem else , url: link_elem.get(href) if link_elem else , } items.append(item) except Exception as e: print(f解析单条数据出错: {e}) return items注意CSS选择器这地方不同时间段、不同页面状态下类名可能会变。我写这篇文章时用的选择器到你实际操作时不一定还能用。关键是掌握方法在开发者工具里右键商品块选择“检查”确认实际用的类名或属性再改到代码里。这属于正常维护工作别指望一套选择器用到天荒地老。3.3 详情页补全获得完整商品信息列表页拿到的是“缩略版”数据真要分析用还得进详情页拿完整字段。详情页URL可以从列表页的链接中取得一般是https://detail.1688.com/offer/xxxxxx.html这样的格式其中xxxxxx就是商品ID。详情页的数据结构比列表页复杂得多。我记得自己扒的时候价格信息往往不是直接渲染在HTML里的而是存在于页面内嵌的JavaScript变量中比如window.__INIT_DATA__或者iDetailConfig之类的字段。遇到这种情况我的处理思路是先把整个HTML拿下来然后用正则去做一个“粗提取”——定位到包含商品数据的关键JSON区块再用json.loads去解析。import re import json def fetch_detail(offer_id): url fhttps://detail.1688.com/offer/{offer_id}.html resp requests.get(url, headersHEADERS, timeout10) resp.encoding resp.apparent_encoding html resp.text # 尝试从页面提取JSON数据块 m re.search(rwindow\.__INIT_DATA__\s*\s*(\{.*?\});, html, re.S) if not m: m re.search(riDetailConfig\s*\s*(\{.*?\});, html, re.S) if m: try: data json.loads(m.group(1)) # 这里根据自己的需要逐层提取字段 return data except Exception as e: print(fJSON解析失败: {e}) return None return None详情页这块我的建议是不要执着于一次把所有字段拿全。先跑通一个商品把提取结果打印出来对照网页核对一遍确认字段映射正确之后再放开循环去批量跑。批量跑的时候一定要实时核对数据和页面是否一致因为1688偶尔会有AB测试页面不同请求返回的结构可能不一样。3.4 调度、限速与断点续采采集不是“一把梭”地跑循环那样大概率会被平台限制。我总结了一套低风险调度策略核心是三条原则第一随机延时。每次请求之后使用time.sleep(random.uniform(2, 5))代替固定值模拟人类浏览行为。实测下来固定延时容易触发频率检测机制加了随机抖动之后稳定性明显提升。第二记录进度。我在本地维护一个collected_ids.txt文件每成功采集一个商品ID就往里面追加一行。脚本启动时先加载这个文件已经采集过的商品直接跳过。这样就算中途挂了、断网了、电脑重启了都不用从头再来。第三设置重试上限。单次请求失败超时、返回异常状态码时最多连续重试3次每次间隔递增。如果连续失败超过3次说明很可能触发了平台的访问限制直接停止脚本等上一段时间再继续而不是死磕到底。def robust_request(url, headers, max_retries3): for i in range(max_retries): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp else: print(fHTTP {resp.status_code}, 重试 {i1}) except requests.RequestException as e: print(f请求异常: {e}, 重试 {i1}) time.sleep(random.uniform(3, 6)) return None3.5 数据清洗与导出Excel采集到的原始数据不能直接拿来用原因是网页上展示的信息是给人看的格式不是给程序用的格式。我遇到过的情况有三类价格字段是“1.20-2.50”这种区间文本销量是“1.2万”这种缩写标题里夹杂着一堆营销文案和特殊字符。所以采集之后一定要做数据清洗。我的清洗逻辑是价格区间拆分成最低价和最高价两列方便数据分析销量统一转成数字识别“万”“亿”这样的单位后缀标题里的换行、多余空格、特殊符号全部去掉空字段填充默认值避免后续Excel统计报错import pandas as pd def clean_data(df): # 价格拆分为最低价和最高价 df[[price_min, price_max]] df[price].str.extract(r([\d.])\s*-\s*([\d.])) # 销量转数字 def parse_sales(s): if isinstance(s, str): if 万 in s: return float(s.replace(万, ).replace(, )) * 10000 if 亿 in s: return float(s.replace(亿, ).replace(, )) * 100000000 return float(s.replace(, )) return s df[sales_num] df[sales].apply(parse_sales) return df # 用 utf-8-sig 编码写Excel避免中文乱码 df pd.DataFrame(all_items) df clean_data(df) df.to_excel(1688_products.xlsx, indexFalse, encodingutf-8-sig)这里有个小知识点用 pandas 写 Excel 时数据中的中文在部分环境下会乱码虽然to_excel没有encoding参数但你可以先把 DataFrame 写入 CSV 时用utf-8-sig或者直接通过 Excel 模板方式处理。最常见的做法是将中间结果保存为 CSV 文件编码用utf-8-sig这样双击打开CSV时中文就不会乱码。如果你确实需要 xlsx 格式也可以先存CSV再用 Excel 打开另存为 xlsx。4. 实测中遇到的典型问题与避坑经验4.1 请求频繁导致IP被临时限制这是所有采集者都会遇到的问题1688 对异常流量检测相当敏感。最典型的症状是前几十个请求一切正常突然某一次请求返回的页面里没有商品数据取而代之的是一段包含“安全验证”或“访问异常”的HTML。我实测下来的应对策略是这样的降低单次循环内的请求频率把间隔从2秒拉长到4到6秒加一个“熔断机制”——如果连续5次请求都返回异常页面就自动停止脚本半小时再跑。这种情况下不要试图去“攻克”验证码那是明确的违规操作老老实实限速等待才是正路。提示这个问题的核心不在于“技术对抗”而在于你的采集行为是否会给目标平台带来负担。保持采集行为接近人工浏览节奏既能保证任务完成又能体现对平台规则的尊重。每次我把这个逻辑讲给团队新人听都会加一句爬虫是工具不是武器。4.2 页面数据动态加载requests直接拿不到1688 的部分页面会通过接口动态加载数据你直接用 requests 拿到的是页面骨架商品数据是空的。新手上路最容易卡在这一步。我当时的解决思路是用浏览器开发者工具去定位真正的数据接口。F12 打开 Network 面板勾选 XHR 过滤器重新加载页面找到返回JSON数据的请求。比如搜索页的商品列表我踩坑后发现它其实是从一个offer_search接口返回的JSON数据HTML 里只有一部分兜底数据。找到这个接口之后直接请求接口地址解析效率比解析HTML高得多还能避免一些页面的反爬逻辑。如果你的目标页面实在找不到XHR接口只有JS渲染才能拿到数据那就只能用 Playwright 方案。它的思路是启动一个无头浏览器等页面执行完JavaScript之后再提取数据。缺点很明显慢内存占用大。能用接口解决的绝不轻易上浏览器自动化。4.3 字段提取不准或漏数据做采集最头疼的问题之一就是解析规则失效。同一个CSS选择器上午跑得好好的下午可能就取不到数据了。原因可能是页面改版、类名调整也可能是部分商品卡片结构异常。我的排查步骤是先把出问题的页面HTML保存下来检查那一条商品卡片和正常的卡片有什么结构差异实在找不到差异就在代码里加日志打印出当前解析的原始HTML片段肉眼对比。这种问题没有一劳永逸的解法只能做好异常兜底——每条数据解析都用 try/except 包住单条失败不影响整体流程。4.4 中文乱码与数据编码问题Python 的 requests 库有时候自动推断编码会出错。1688 页面编码基本是UTF-8但某些接口返回的编码标识不准确导致解析出来的中文全是乱码。我的做法是拿到响应后先手动指定编码再解析文本不要依赖自动推断。代码里就是resp.encoding utf-8或者resp.encoding resp.apparent_encoding先用前者没有效果再试后者。导出到 CSV 时用utf-8-sig而不是utf-8这个细节能帮你省掉大量Excel打开乱码的烦恼。4.5 数据去重与更新策略如果你不是只跑一次而是会定期采集比如每周跑一次做价格监控那数据去重和更新策略就很重要。我用的是“商品ID 采集日期”作为唯一键同一商品ID当天只保留最新一条记录新采集的数据进库前先查一下这个ID是否已存在存在就更新价格和销量字段不存在就插入新记录。这样你的数据集只会越来越大但每条商品的数据始终是最新的。实操心得做定期采集时把采集日期直接加到每条数据里分析价格波动趋势会非常方便。这个设计一开始就做好后面分析会省很多事。5. 采集效率优化从能跑到跑得快5.1 使用并发还是老老实实单线程这是一个经常被问的问题。很多人写完单线程版本之后觉得太慢第一反应就是上多线程。但我建议先评估你的真实需求采集500条商品数据用2到5秒的间隔全部跑完也就十几分钟其实完全可以接受。如果确实需要采集几万条再考虑并发。我实测下来用ThreadPoolExecutor做有限并发同时控制并发数在3到5个线程以内加上限速对平台的影响不大采集速度也能提升3到4倍。但一定要设置合理的并发上限并做好重试机制否则一旦触发平台限制反而更慢。from concurrent.futures import ThreadPoolExecutor def fetch_one(offer_id): detail fetch_detail(offer_id) # 处理细节 return detail with ThreadPoolExecutor(max_workers3) as executor: results list(executor.map(fetch_one, offer_ids))5.2 数据存储CSV够用但别迷信Excel数据量小的时候CSV 完全够用。但数据量到了几万条Excel 文件打开都会卡。我的建议是分阶段处理采集阶段统一写CSV分析阶段用 pandas 读取需要做可视化再摘取关键字段到Excel模板里。如果你技术能力强一点可以直接把数据存到 SQLite查询、去重、更新都方便得多而且不存在打开大文件卡死的问题。5.3 日志监控别让脚本黑盒跑采集脚本一跑就是几十分钟中间出了什么问题你看不到等跑完才发现数据有缺失那才是最崩溃的。我给自己的脚本加了一套简单的日志系统使用logging模块把每次请求的URL、状态码、解析条数、异常信息都记录到日志文件里。跑完脚本之后先查看日志里有没有大量异常再去做数据校验。这个习惯帮我省了至少两三次“从头再跑一遍”的时间成本。6. 一些掏心窝子的经验写到这里技术上的内容基本讲透了。最后想跟你聊几句实在话。爬虫技术本身是中性的你用得好它是解放生产力的工具用不好就会变成给自己挖坑的铲子。我做过不少数据采集项目一个深切的体会是有边界感的爬虫反而跑得更远。控制请求频率、只采公开数据、遵守平台规则这些约束看似让采集变“慢”了实际上是在保护你的IP、保护你的数据质量更重要的是保护你的心态——不用整天提心吊胆担心被封、被警告。另外1688 页面结构是会变的今天能跑通的代码下周可能就失效了。这不是你的问题是所有爬虫开发者都要面对的现实。别慌按照开发者工具找接口、定位变量、调整选择器这个方法论去做很快就能修好。真正值钱的不是某一段代码而是你面对页面变化时的排查思路。如果你之前只会手动复制粘贴这篇内容能帮你打开新世界的大门如果你已经写过一些爬虫希望里面关于限速策略和断点续采的部分能帮你少走弯路。做采集这件事慢就是快稳就是快。祝你的脚本一次跑通数据干干净净。
返回列表