☰
1688商品批量采集实战:Python爬虫全流程详解
2026/10/6 3:31:34 网站建设 项目流程

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/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", } SESSION = requests.Session() def fetch_html(url, max_retries=3): for attempt in range(max_retries): try: resp = SESSION.get(url, headers=HEADERS, timeout=10) if resp.status_code == 200: resp.encoding = resp.apparent_encoding return resp.text else: print(f"HTTP {resp.status_code}: {url}") except requests.exceptions.Timeout: print(f"Timeout: {url} (attempt {attempt + 1}/{max_retries})") except requests.exceptions.RequestException as e: print(f"Request 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(strip=True) 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(strip=True) if price_elem else "" min_order_elem = card.select_one(".product-min-order") min_order = min_order_elem.get_text(strip=True) if min_order_elem else "" shop_elem = card.select_one(".shop-name") shop_name = shop_elem.get_text(strip=True) 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(strip=True) if price_range else "" # 尝试解析起批量 moq_elem = soup.select_one(".moq-num") moq = moq_elem.get_text(strip=True) if moq_elem else "" # 尝试从页面内嵌的JSON中提取更完整信息 script = soup.find("script", string=lambda 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_workers=3) 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, filename="products.csv"): df.to_csv(filename, index=False, encoding="utf-8-sig") print(f"Saved {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处理就比较吃力了,打开文件卡半天,筛选也慢。这时候建议上SQLite,Python自带这个库,不需要额外安装服务,一个文件就是一个数据库,非常适合个人项目和中小型数据量。

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用crontab,Windows用任务计划程序,就能实现每天定时自动采集,数据自动更新。

6.2 工具选型也可以反着来:什么时候不用自己写爬虫

写到这里,顺便提一句场景判断:不是所有采集需求都值得写代码。如果你只是临时要一二百条数据,用浏览器插件或者现成的采集工具反而更快,拖拽点选就能导出Excel,根本不用折腾环境。

我自己判断的标准很简单:一次性需求、数据量小,用现成工具;需求反复出现、数据量中等以上、需要后续自动化,才值得写脚本。千万不要为了爬而爬,为了一百条数据花半天功夫写代码,这本身就不经济。

6.3 关于爬虫这件事,我的几点个人心得

文章最后,聊点代码之外的东西。

我做了这么多年数据采集,最大的体会是:爬虫的技术难点从来都不是“怎么绕过限制”,而是怎么把一件事做得稳定、优雅、可持续。真正的高手不是脚本跑得最快的人,而是脚本服役时间最长的人。控制频率、处理好异常、给数据做好清洗和去重,这些才是长期价值的来源。

还有一点是关于合规和分寸感的。我在第三节的内容里多次提到基本边界,是因为我一直觉得,做技术的人应该对自己的行为边界有清醒的认知。用在正途上,爬虫是高效的信息整理工具;用偏了,可能带来法律风险。把技术用在学习、研究和合理的数据分析上,才能真正让自己成长。

对想入门爬虫的朋友,我的建议是:先用小目标练手,比如采集一两百条商品数据,把整个流程完整跑通。跑通之后,再考虑框架、并发、分布式这些进阶方向。路是一步一步走出来的,数据也是一条一条抓出来的。祝你在数据采集这条路上少踩坑,多收获。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询