
1. 项目背景为什么需要批量采集1688商品数据1.1 手动复制粘贴的痛点做过的人都懂做电商选品、做市场调研、做竞品分析绕不开一件事盯着1688的商品页一条条看把标题、价格、起批量、销量、店铺名复制到表格里。一天下来眼睛花手指酸效率却低得可怜。几十个商品还好说几百个上千个商品呢数据量大一点手动操作基本等于加班到天亮。更让人崩溃的是手动复制出来的数据质量参差不齐。商品的标题长短不一价格区间写法五花八门有的带单位有的不带复制到表格里格式乱成一锅粥。后边还得花大量时间做数据清洗。这个过程浪费的时间其实比复制本身还要多。所以当“批量采集1688商品数据”这个需求出现时本质上不是想偷懒而是想把人力从重复劳动里解放出来让数据更规范、更完整、更及时。这个需求在选品分析、供应链比价、店铺运营、行业调研里都很常见。本文就把我实际跑通的一套完整流程分享出来包括工具选型、核心代码、避坑思路和合规边界照着做就能把整个流程跑起来。1.2 批量采集的典型应用场景在聊技术细节之前先看看这个需求到底在什么场景下最常用。不搞清楚“为什么采集”很容易陷入为了爬而爬的误区。第一个场景是选品分析。很多做跨境或者国内电商的朋友会在1688上筛潜力款先看哪些品类在平台上有热度再看供应商的起批量、报价、回头率综合判断这个品值不值得做。这些数据维度跨多个商品页手工一个个看很难横向对比采集到表格里才好统一分析。第二个场景是价格监控。批发平台的价格不是一成不变的供应商会做促销会调整阶梯价。如果你正在和几家供应商比价光靠截图存档不现实定一个定时任务周期性地把价格跑一遍谁涨价了谁降价了一目了然。第三个场景是供应链整理。有一定规模的卖家SKU可能上百个每个SKU对应多个供应商。把这些信息采集下来整理成数据库后续做库存管理、补货计划、成本核算都有据可循。这四个字概括下来就是“把分散的信息结构化”。掌握了这个思路不管你采集的是1688还是别的平台核心方法论是通用的。1.3 动手之前先想清楚合规边界讲到爬虫最绕不开的就是合规问题。很多新手上来就问“怎么绕过验证码”“怎么突破频率限制”这个思路本身就偏差了。我必须先把话说在前面技术没有原罪但用技术的人有边界。批量采集公开商品信息用于个人学习、市场调研、经营参考这个场景本身是常见的但如果采集频率过高影响对方网站正常服务或者把采集来的数据直接打包倒卖那就踩线了。尤其注意《数据安全法》《个人信息保护法》对数据采集和使用的合规性有明确要求商品数据虽多属公开信息但涉及用户个人信息的部分绝不能碰。所以本文的所有技术方案都以“低频率、小规模、自用学习”为前提。代码里也会重点讲限速、超时和异常处理这不是为了教大家怎么“安全地偷”而是真正专业的人都知道保持克制才能长期稳定。2. 工具选型与运行环境准备2.1 为什么选择Python Requests BeautifulSoup工欲善其事必先利其器。做网页数据采集编程语言和库的选型会直接决定开发效率。目前市面上主流的方案无非是Python系、Node.js系以及零代码的采集工具。我个人的选择是Python搭配Requests和BeautifulSoup这套组合对新手友好对老手也够用。Python语法简单写起来不像Java那么啰嗦Requests库是HTTP请求的事实标准封装了cookie、session、重定向这些琐碎细节十来行就能发起一次像模像样的HTTP请求BeautifulSoup的解析API用起来非常直观find和find_all基本能覆盖九成以上的页面解析需求。对比其他方案Scrapy功能强大但学习曲线较陡要理解Spider、Item Pipeline、Middlewares这些抽象概念小规模采集有点杀鸡用牛刀Node.js的Puppeteer适合渲染复杂的JS页面但浏览器自动化开销大内存占用高效率反而不如纯HTTP请求加解析。所以如果你的目标页面是普通列表页加详情页这种结构Requests BeautifulSoup是性价比最高的起点。2.2 环境搭建与依赖安装环境准备其实很简单。只要你的电脑装了Python 3.7以上版本在终端执行两行命令就能搞定pip install requests beautifulsoup4 lxml pandas这里多说一句lxml是BeautifulSoup的解析器后端解析速度比Python内置的html.parser快很多数据量大时能明显感知到差距。pandas是后边做数据清洗和导出CSV用的一步到位装上省得后面再补。开发IDE我用的是VS Code配合Python插件体验不错。有的朋友喜欢用PyCharm也可以看个人习惯工具不挑思路通了用记事本都能写。2.3 一套健壮的请求函数的必备要素写爬虫很多人第一版代码就是requests.get(url)一把梭。跑通几个页面没问题一旦遇到超时、被拒绝、返回异常直接傻眼。真正能稳定运行的关键在于请求函数本身要健壮。一个合格的请求函数至少要包含下面几块内容设定User-Agent和其他基础Header让请求看起来像一个正常浏览器设定超时时间防止某个页面卡死把整个程序拖垮捕获常见的网络异常比如连接超时、读取超时的处理加上简单的重试逻辑网络抖动时自动重试比如三次加入随机延时控制请求频率。这些听起来是细节但直接影响整个爬虫的稳定性。我实际测过很多次一个没做超时处理的脚本跑几百个页面后大概率会卡死在某一个请求上加了超时和重试整体稳定性能上一个台阶。后面章节我会给出完整的代码实现这里先建立一个认知爬虫工程的核心不是怎么把数据抓下来而是怎么稳定地把数据抓完。3. 采集前的页面分析与数据字段设计3.1 先弄懂目标页面的结构再写一行代码打开1688的商品搜索列表页按F12打开开发者工具你先不要急着找接口先把页面的DOM结构看清楚。一般来说商品列表页上每个商品卡片会包含商品标题、主图、价格区间、起批量、成交额或回头率、店铺名称。每个卡片外层的class名称虽然比较混乱但仔细观察还是有规律的商品标题大多在带title属性的a标签里价格在特定class名的span里。我的习惯是在开发者工具里直接选中某个商品标题的元素看它的父级和兄弟节点关系一步步理清数据挂载位置。这一步不能在代码里偷懒必须人工分析清楚。页面结构分析错了后面代码写再多都是白搭。另一个更高效的方法是直接看接口。很多网站的商品数据并不是服务端渲染的HTML而是前端通过Ajax请求拉JSON数据再动态渲染。这种时候你直接请求列表页拿到的HTML里可能根本没有商品数据或者只有第一屏的数据。按F12切到Network刷新页面筛选XHR请求看有没有返回JSON的接口如果找到了那代码写起来反而更简单直接解析JSON即可完全不用管HTML结构。3.2 数据字典采集之前先列好字段清单很多新手采集数据代码写完了才想到“我到底要哪些字段”结果不是多抓就是漏抓后期补数据极其浪费时间。我的建议是动手写代码前先用表格把数据字典定下来。所谓数据字典就是明确采集哪些字段、字段是什么类型、存到CSV里的列名叫什么。比如一个标准的商品数据结构长这样字段名类型说明titlestring商品完整标题price_minfloat最低价price_maxfloat最高价min_orderint起批量sales_countstring销量或成交额展示文本shop_namestring店铺名称product_urlstring商品详情页链接image_urlstring主图链接字段清单列好之后解析代码的写起来就有章可循了每个字段对应一个解析函数最后统一组装成字典。这个习惯在做多页面采集时尤其重要因为字段一多就很容易乱没有清单约束写到后面自己都不记得哪个字段对应哪个解析逻辑。3.3 列表页与详情页的分工协作1688的场景里列表页能拿到的信息已经不少但价格区间、起订量这些信息往往在详情页才有更完整的版本。比如列表页可能只显示“¥5.00-10.00”但起批量对应的价格阶梯必须点进详情页才能看到。所以在架构上我采用“列表页 详情页”两层采集模式。列表页负责批量获取商品的基础数据和详情页链接然后筛选出重点关注的部分商品进入详情页抓取更完整的数据。这里要给一个非常实操的建议除非你确实需要每个商品的完整数据否则不要对列表页里的每个商品都去请求详情页。一次列表页翻10页每页假设40个商品就是400个详情页请求再加上列表页本身10次请求流量和耗时瞬间上升一个量级。对服务器压力大对你自己也没好处。一般我会先解析列表页把数据存下来然后人工或半自动地筛选一批目标商品再补充详情页采集。4. 核心代码实战从请求到CSV落地4.1 第一步写好一个带重试和限速的请求函数这是整个采集脚本的地基我直接给一个封装好的版本你在自己的项目里改一下Header和URL就能用。import requests import time from random import uniform 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,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } SESSION requests.Session() def fetch_html(url, max_retries3): for attempt in range(max_retries): try: resp SESSION.get(url, headersHEADERS, timeout10) if resp.status_code 200: resp.encoding resp.apparent_encoding return resp.text else: print(fHTTP {resp.status_code}: {url}) except requests.exceptions.Timeout: print(fTimeout: {url} (attempt {attempt 1}/{max_retries})) except requests.exceptions.RequestException as e: print(fRequest error: {e} (attempt {attempt 1}/{max_retries})) time.sleep(2 attempt * 2) return None注意几个细节。一是timeout设为10秒既能容忍慢响应又不会无限等待。二是resp.encoding resp.apparent_encoding这是为了避免中文乱码尤其重要。三是失败后的等待时间递增第一次退避2秒第二次4秒给服务器留出恢复时间。4.2 第二步解析列表页拿到商品数据和详情页链接列表页解析的核心是用BeautifulSoup找到商品卡片的容器节点。以常见的HTML结构为例product card通常会挂在某个class下然后每个商品项在内部通过循环提取。from bs4 import BeautifulSoup def parse_list_page(html): soup BeautifulSoup(html, lxml) items [] for card in soup.select(.product-card): title_elem card.select_one(.product-title a) if title_elem is None: continue title title_elem.get_text(stripTrue) product_url title_elem.get(href) if product_url.startswith(//): product_url https: product_url price_elem card.select_one(.product-price) price_text price_elem.get_text(stripTrue) if price_elem else min_order_elem card.select_one(.product-min-order) min_order min_order_elem.get_text(stripTrue) if min_order_elem else shop_elem card.select_one(.shop-name) shop_name shop_elem.get_text(stripTrue) if shop_elem else items.append({ title: title, price_text: price_text, min_order: min_order, shop_name: shop_name, product_url: product_url, }) return items这里特别说一下选择器。实际操作中1688商品卡片的具体class名可能会变所以这段代码里的.product-card、.product-title这类类名是一个示意。你要做的是打开开发者工具找到真实的class名替换进去。最简单的方法是直接复制元素路径但建议你看懂结构再写不然一个class调整就抓瞎。还有一个小坑1688的图片链接、详情链接经常是相对路径或者不带协议的//开头拼接时要处理一下。不处理的话跳转详情页时requests会报错。4.3 第三步进入详情页补全价格阶梯与库存信息详情页的数据往往比列表页丰富关键是价格区间和起订量的具体数值。详情页的结构各家店铺定制化程度很高解析起来比列表页要麻烦一些。如果详情页的数据也是动态加载的建议优先在Network里找JSON接口。找到接口后直接请求即可返回的JSON结构清晰字段名规范省去了解析HTML的麻烦。这个方案的缺点是接口参数比较复杂需要花时间逆向一下参数拼接规则。import json def parse_detail_page(html): soup BeautifulSoup(html, lxml) # 尝试解析价格区间 price_range soup.select_one(.price-range) price_text price_range.get_text(stripTrue) if price_range else # 尝试解析起批量 moq_elem soup.select_one(.moq-num) moq moq_elem.get_text(stripTrue) if moq_elem else # 尝试从页面内嵌的JSON中提取更完整信息 script soup.find(script, stringlambda t: t and price in t) if script: try: data json.loads(script.string) # 根据实际数据结构做字段提取 except json.JSONDecodeError: pass return { price_range: price_text, moq: moq, }这段代码里内嵌JSON的解析也不是必选项取决于页面是否有合适结构。真正的重点是先把基础字段抓到拿到数据后做一层清洗和去重再决定要不要追求更细的字段。4.4 第四步多线程还是异步批量采集的正确打开方式批量采集自然会想到并发。很多新手一上来就开50个线程结果除了把对方服务器搞404之外没有任何收益。这里必须明确一个原则控制并发的核心目的不是越快越好而是稳定不封。当前的主流做法有三个单线程循环、ThreadPoolExecutor多线程、asyncio异步。单线程最安全代码最简单但速度确实慢。我自己的习惯是控制并发在3到5个线程之间既不显得太暴力速度也还能接受。用concurrent.futures模块操作起来很直观from concurrent.futures import ThreadPoolExecutor, as_completed def fetch_and_parse(url): html fetch_html(url) if html is None: return None return parse_detail_page(html) with ThreadPoolExecutor(max_workers3) as executor: futures [executor.submit(fetch_and_parse, item[product_url]) for item in items] for future in as_completed(futures): result future.result() if result: print(result)不推荐一上来就上异步因为异步的代码调试难度和心智负担都更高而且你处理的瓶颈往往不在程序本身而在网络延迟和对方服务器的限制。先用小并发跑通再根据实际情况调整才是务实的路径。4.5 第五步数据清洗与CSV导出采集到的原始数据通常不能直接用。价格字段里可能混着“¥”符号和“起”字销量字段可能是“100件成交”这种带后缀的文本。所以要把清洗逻辑单独拆出来。我用pandas来做清洗和导出便捷又不容易出错import pandas as pd import re def clean_price(price_text): 从价格文本里提取最低价和最高价 if not price_text: return None, None prices re.findall(r\d\.?\d*, price_text) if not prices: return None, None if len(prices) 1: return float(prices[0]), float(prices[0]) return float(prices[0]), float(prices[1]) def build_dataframe(records): df pd.DataFrame(records) if df.empty: return df # 价格清洗示例 df[[price_min, price_max]] df[price_text].apply( lambda x: pd.Series(clean_price(x)) ) return df def export_to_csv(df, filenameproducts.csv): df.to_csv(filename, indexFalse, encodingutf-8-sig) print(fSaved {len(df)} rows to {filename})这里重点提醒一下导出CSV时编码务必用utf-8-sig直接用utf-8的话用Excel打开会出现中文乱码。这是无数人踩过坑的地方。数据量不大时用CSV完全够了数据量大了建议换成SQLite或者MySQL后文会简单说。5. 常见问题与反爬排查实录5.1 请求返回403或302大概率不是被封是Headers不对403是新手最容易碰到的状态码。很多人第一反应是“IP被ban了”其实大部分时候根本不是。在本地调试阶段最大的嫌疑就是User-Agent或Referer没设置服务器识别出你不是正常浏览器直接拒绝响应。我排查403的顺序是先用浏览器无痕模式打开同一个URL确认页面能正常访问排除URL本身的问题再检查Headers看User-Agent是不是浏览器版本很多网站对非浏览器UA零容忍然后检查是否缺少Referer尤其是详情页接口Referer往往要指向当前页面最后才考虑是不是请求频率过高被限流这时候停一会儿再试。302则更多是登录态或Cookie问题。1688很多页面需要登录才能看全数据如果未登录被重定向到登录页抓回来的HTML就变成了登录页面解析逻辑自然全部失效。这种情况下把浏览器上登录后的Cookie复制到requests的Headers里就能拿到登录态数据。5.2 页面里找不到商品数据大概率是动态渲染要找接口这种情况在带搜索和筛选功能的列表页非常常见。页面是前端通过Ajax拿数据后渲染的直接请求URL返回的HTML里除了框架代码什么都没有。遇到这种情况优先找XHR接口。打开开发者工具切到Network刷新页面筛选XHR或Fetch请求找返回类型为JSON的请求。点开一个看Preview如果能看到商品列表数据就顺着这个接口的参数逆向分析。常见的参数有关键词、页码、排序方式找到规律后直接用requests请求接口解析JSON比解析HTML省事太多。接口请求要注意一个坑接口的请求头里往往有专门的Header比如X-Requested-With、Content-Type甚至会有动态生成的token。这些都要在Headers里带上否则接口可能拒绝访问。5.3 请求多了被限流降低频率、加随机延时比换IP更管用做爬虫必然躲不开频率控制。很多新手一遇到限流就想换IP这个思路太“重”了而且代理IP池水很深质量参差不齐便宜的代理连接慢还不稳定反而增加问题。我的实践经验是先用优雅的频率解决问题。默认加1到3秒的随机延时让请求时间点分布得比较平缓。如果还是被限再逐步增加到5秒、8秒。对绝大多数个人学习场景来说5秒的间隔足够温和了。加上前面说的重试机制和处理退避稳定性会好很多。再说一句实话采集本来就是和数据源的一场持久战。你把频率控制在合理范围对方服务器也轻松你的脚本也能跑得久双赢。5.4 数据出现空缺或错位要么是字段变了要么是解析选择器太脆弱跑采集脚本最怕的不是报错而是不报错但数据全是错的。最常见的就是页面改版class名变了旧选择器匹配不到元素解析结果为空但程序本身不会报错。所以我强烈建议在解析完成后加一道校验逻辑统计每条记录的字段是否完整有没有空值比例过高的字段标题长度是否合理价格是否在正常区间。一旦发现异常先别急着入库打印几行原始HTML出来检查选择器。另外“解析选择器太脆弱”也是一个大坑。比如直接用.product-title这样的类名页面只要调整class就全挂。更稳的方式是同时写多个备用选择器按顺序尝试一旦哪个匹配到就用哪个。这种方式虽然代码量多一点但能显著提高脚本的生存周期。6. 扩展思路与长期稳定的心得6.1 数据量大怎么办从CSV升级到数据库与定时任务当采集的商品数量超过几千条再用Excel处理就比较吃力了打开文件卡半天筛选也慢。这时候建议上SQLitePython自带这个库不需要额外安装服务一个文件就是一个数据库非常适合个人项目和中小型数据量。import sqlite3 conn sqlite3.connect(products.db) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS products ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, price_min REAL, price_max REAL, min_order INTEGER, shop_name TEXT, product_url TEXT UNIQUE ) ) conn.commit()注意product_url加UNIQUE约束这样重复采集时就不会插入重复数据天然做了去重。数据库建好之后再配合定时任务Linux用crontabWindows用任务计划程序就能实现每天定时自动采集数据自动更新。6.2 工具选型也可以反着来什么时候不用自己写爬虫写到这里顺便提一句场景判断不是所有采集需求都值得写代码。如果你只是临时要一二百条数据用浏览器插件或者现成的采集工具反而更快拖拽点选就能导出Excel根本不用折腾环境。我自己判断的标准很简单一次性需求、数据量小用现成工具需求反复出现、数据量中等以上、需要后续自动化才值得写脚本。千万不要为了爬而爬为了一百条数据花半天功夫写代码这本身就不经济。6.3 关于爬虫这件事我的几点个人心得文章最后聊点代码之外的东西。我做了这么多年数据采集最大的体会是爬虫的技术难点从来都不是“怎么绕过限制”而是怎么把一件事做得稳定、优雅、可持续。真正的高手不是脚本跑得最快的人而是脚本服役时间最长的人。控制频率、处理好异常、给数据做好清洗和去重这些才是长期价值的来源。还有一点是关于合规和分寸感的。我在第三节的内容里多次提到基本边界是因为我一直觉得做技术的人应该对自己的行为边界有清醒的认知。用在正途上爬虫是高效的信息整理工具用偏了可能带来法律风险。把技术用在学习、研究和合理的数据分析上才能真正让自己成长。对想入门爬虫的朋友我的建议是先用小目标练手比如采集一两百条商品数据把整个流程完整跑通。跑通之后再考虑框架、并发、分布式这些进阶方向。路是一步一步走出来的数据也是一条一条抓出来的。祝你在数据采集这条路上少踩坑多收获。