南网商城作为南方电网旗下核心的 B2B 电力工业品电商平台,聚焦高压设备、电线电缆、电工材料等专业品类,其商品列表数据(如品类分布、价格区间、供应商资质等)是电力行业采购决策、供应链分析、市场监控的核心支撑。与常规电商平台不同,南网商城未开放公开官方 item_search API,开发者需通过关键词搜索页面解析的方式实现商品列表获取。本文将从基础认知、前置准备、核心流程、进阶优化到合规风控,全方位拆解对接逻辑,帮助开发者构建稳定、高效的电力行业商品搜索数据采集系统。
一、接口基础认知:核心功能与应用场景
1.1 核心功能定义
基础信息:商品 ID、标题(含型号规格)、主图 URL、详情页链接、所属类目
价格信息:含税单价、最小起订量、批量折扣规则、运费说明(工业物流特性)
供应商信息:企业名称、核心资质(ISO9001、电力行业准入认证)、所在地
核心属性:产品类型(如 “高压开关柜”“充电桩”)、执行标准(如 GB/T 12706)、适用场景
交易指标:成交记录概览(如 “已售 300+”)、评价数、供货能力标签
1.2 典型应用场景
采购比价:通过关键词 “10kV 变压器” 搜索,获取多家供应商报价与技术参数,辅助招标选型
品类分析:搜索 “电线电缆” 类目,统计主流品牌、价格分布、规格型号覆盖率
供应商筛选:按 “ISO9001 认证” 筛选条件,提取符合资质的供应商商品列表
市场监控:跟踪 “新能源电力设备” 等新兴品类的上架动态与价格波动
供应链调研:分析特定区域(如 “广东”)电力物资供应商的商品供给结构
1.3 接口核心特性
行业专业性:数据聚焦电力工业场景,包含大量专业技术参数与行业标准字段
非官方属性:无公开 API 文档,依赖 HTML 页面解析,页面结构稳定性较高(工业平台更新频率低)
反爬机制:主要包含 IP 访问频率限制、User-Agent 校验、部分页面需登录态 Cookie
数据形态:以静态 HTML 嵌入为主,分页数据、筛选结果无复杂动态加载(AJAX 依赖度低)
权限限制:部分高价值数据(如精准成交价格)需企业实名认证后可见
二、对接前置准备:环境、工具与页面分析
2.1 开发环境配置
2.1.1 推荐技术栈
开发语言:Python(高效的 HTML 解析与反爬处理生态,适合快速迭代)
核心库选型:
网络请求:requests(同步请求,适合小规模数据获取)、aiohttp(异步请求,提升批量采集效率)
页面解析:BeautifulSoup(简洁易用,处理静态 HTML 结构)、lxml(高效 XPath 解析,适配表格类数据)
反爬工具:fake_useragent(随机生成浏览器 User-Agent)、proxy_pool(代理 IP 池管理)、time(请求频率控制)
数据处理:re(正则提取型号、价格等结构化数据)、pandas(结果数据格式化与导出)
辅助工具:Selenium(模拟登录与复杂筛选操作,应对动态渲染场景)、Postman(请求测试与抓包分析)
2.1.2 环境搭建示例(Python)
# 安装核心依赖库pip install requests beautifulsoup4 lxml fake-useragent aiohttp pandas
2.2 搜索页面结构解析
2.2.1 URL 格式规律
https://www.nwmall.com/search?keyword={关键词}&page={页码}&{筛选参数}关键词(keyword):URL 编码后的搜索词,如 “高压电缆” 编码为
%E9%AB%98%E5%8E%8B%E7%94%B5%E7%BC%86页码(page):默认从 1 开始,每页商品数量约 20-30 条(平台固定)
筛选参数(可选):通过浏览器筛选后拼接,常见参数如下:
筛选类型 参数名 示例值 说明 价格区间 priceFrom/priceTo priceFrom=1000&priceTo=5000 含税单价范围(元) 供应商资质 qualification qualification=ISO9001 支持多资质拼接(用逗号分隔) 商品类目 categoryId categoryId=123 类目 ID 从平台分类页提取 排序方式 sortType sortType=price_asc price_asc(低价优先)、sales_desc(销量优先)
https://www.nwmall.com/search?keyword=%E4%B8%80%E5%8D%81kV%E5%BC%80%E5%85%B3%E6%9F%9C&priceFrom=10000&priceTo=50000&page=1
2.2.2 核心数据位置定位(浏览器 F12 分析)
商品列表容器:
<div class="product-list-container">(所有商品卡片的父节点)单商品卡片:
<div class="product-item">(每个卡片对应一个商品)核心字段 XPath 示例:
商品标题:
//div[@class="product-item"]//h3/a/text()商品 ID:
//div[@class="product-item"]//a/@data-id(或从详情页 URL 中提取)含税价格:
//div[@class="product-price"]/span/text()供应商名称:
//div[@class="supplier-name"]/a/text()主图 URL:
//div[@class="product-img"]/img/@src起订量:
//div[@class="min-order"]/text()
2.3 前置准备清单
浏览器抓包工具(Chrome/Firefox 开发者工具):分析页面结构与请求头
代理 IP 资源:准备多个高匿代理(电力行业数据采集需稳定 IP,避免频繁切换)
南网商城账号:完成企业实名认证(部分核心数据需登录后可见)
类目 ID 映射表:从平台分类页(如 “电力设备 → 高压设备”)提取类目 ID,用于精准筛选
请求头模板:包含 User-Agent、Referer、Cookie(登录后从浏览器复制),模拟真实用户访问
三、核心对接流程:从请求构建到数据结构化
3.1 步骤 1:请求参数构建与 URL 生成
3.1.1 核心参数配置
import urllib.parse# 基础配置keyword = "10kV 变压器" # 目标搜索词price_from = 5000 # 最低价格price_to = 50000 # 最高价格page = 1 # 当前页码sort_type = "sales_desc" # 排序方式(销量优先)# 关键词 URL 编码encoded_keyword = urllib.parse.quote(keyword)# 构建筛选参数字典params = {
"keyword": encoded_keyword,
"priceFrom": price_from,
"priceTo": price_to,
"page": page,
"sortType": sort_type}# 生成完整 URLbase_url = "https://www.nwmall.com/search"# 拼接参数(urllib.parse.urlencode 自动处理参数分隔符)full_url = f"{base_url}?{urllib.parse.urlencode(params)}"print("生成 URL:", full_url)3.1.2 请求头构建(模拟真实用户)
from fake_useragent import UserAgent# 随机生成 User-Agentua = UserAgent()headers = {
"User-Agent": ua.chrome, # 模拟 Chrome 浏览器
"Referer": "https://www.nwmall.com/", # Referer 验证(表明来源是平台首页)
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Cookie": "SESSION=xxx; user_token=xxx" # 替换为登录后浏览器的 Cookie(F12 → Application → Cookies)}3.2 步骤 2:发送请求与反爬应对
3.2.1 基础请求发送(requests 同步)
import requestsimport timedef send_request(url, headers):
try:
# 控制请求频率(每 2-3 秒一次,避免触发 IP 限制)
time.sleep(2.5)
response = requests.get(
url=url,
headers=headers,
timeout=10, # 超时时间设置(避免长时间阻塞)
proxies={"http": "http://xxx.xxx.xxx.xxx:8080", "https": "https://xxx.xxx.xxx.xxx:443"} # 代理 IP(替换为实际资源)
)
# 状态码校验(200 表示请求成功)
if response.status_code == 200:
return response.text # 返回 HTML 页面内容
elif response.status_code == 403:
print("警告:IP 被封锁或 Cookie 失效,请更换代理或重新登录")
return None
else:
print(f"请求失败,状态码:{response.status_code}")
return None
except Exception as e:
print(f"请求异常:{str(e)}")
return None# 发送请求html_content = send_request(full_url, headers)3.2.2 反爬进阶策略
IP 池轮换:使用代理池服务(如阿布云、快代理),按请求次数或失败次数自动切换 IP
动态请求间隔:随机生成 1.5-3.5 秒的休眠时间(
time.sleep(random.uniform(1.5, 3.5))),模拟用户操作节奏Cookie 保鲜:定期(如 24 小时)重新登录并更新 Cookie,避免登录态失效
User-Agent 池:维护多浏览器(Chrome、Firefox、Edge)的 User-Agent 列表,随机切换
3.3 步骤 3:页面解析与数据提取
from bs4 import BeautifulSoupimport redef parse_product_list(html):
soup = BeautifulSoup(html, "lxml") # 使用 lxml 解析器(高效)
product_list = [] # 存储结构化商品数据
# 遍历所有商品卡片
for item in soup.find_all("div", class_="product-item"):
product = {}
# 1. 商品基础信息
title_elem = item.find("h3", class_="product-title")
product["title"] = title_elem.get_text(strip=True) if title_elem else ""
product["detail_url"] = title_elem.find("a")["href"] if title_elem and title_elem.find("a") else ""
# 从详情页 URL 提取商品 ID(如 URL:https://www.nwmall.com/product/P123456.html → ID:P123456)
product["item_id"] = re.search(r"/product/([A-Z0-9]+)\.html", product["detail_url"]).group(1) if product["detail_url"] else ""
# 2. 价格与起订量
price_elem = item.find("div", class_="product-price")
product["tax_price"] = re.search(r"¥(\d+\.?\d*)", price_elem.get_text(strip=True)).group(1) if price_elem else ""
min_order_elem = item.find("div", class_="min-order")
product["min_order"] = re.search(r"(\d+)件起订", min_order_elem.get_text(strip=True)).group(1) if min_order_elem else ""
# 3. 供应商信息
supplier_elem = item.find("div", class_="supplier-name")
product["supplier"] = supplier_elem.get_text(strip=True) if supplier_elem else ""
qualification_elem = item.find("div", class_="qualification-tags")
product["qualification"] = [tag.get_text(strip=True) for tag in qualification_elem.find_all("span")] if qualification_elem else []
# 4. 核心属性(规格型号、执行标准)
spec_elem = item.find("div", class_="product-spec")
if spec_elem:
spec_text = spec_elem.get_text(strip=True)
product["spec_model"] = re.search(r"规格:([^,,]+)", spec_text).group(1) if re.search(r"规格:([^,,]+)", spec_text) else ""
product["exec_standard"] = re.search(r"标准:([^,,]+)", spec_text).group(1) if re.search(r"标准:([^,,]+)", spec_text) else ""
# 5. 交易数据
sales_elem = item.find("div", class_="sales-count")
product["sales_count"] = re.search(r"已售(\d+)\+", sales_elem.get_text(strip=True)).group(1) if sales_elem else "0"
product_list.append(product)
return product_list# 解析商品列表if html_content:
products = parse_product_list(html_content)
print(f"第 {page} 页提取商品数:{len(products)}")
# 打印第一条商品数据示例
if products:
print("商品数据示例:", products[0])3.4 步骤 4:分页处理与数据汇总
page 参数控制,需先获取总页数再批量爬取:def get_total_pages(html):
soup = BeautifulSoup(html, "lxml")
# 定位分页容器(如 <div class="pagination"> 下的最后一个页码)
pagination_elem = soup.find("div", class_="pagination")
if not pagination_elem:
return 1 # 无分页表示只有 1 页
# 提取所有页码文本
page_nums = [int(span.get_text(strip=True)) for span in pagination_elem.find_all("span") if span.get_text(strip=True).isdigit()]
return max(page_nums) if page_nums else 1# 批量爬取多页数据def batch_crawl(keyword, price_from, price_to, sort_type, max_pages=None):
all_products = []
# 先获取第 1 页数据与总页数
first_page_html = send_request(full_url, headers)
if not first_page_html:
return all_products
total_pages = get_total_pages(first_page_html)
# 限制最大爬取页数(避免数据量过大)
if max_pages and total_pages > max_pages:
total_pages = max_pages print(f"总页数:{total_pages},开始批量爬取...")
# 爬取第 1 页
first_page_products = parse_product_list(first_page_html)
all_products.extend(first_page_products)
# 爬取后续页面(从第 2 页到总页数)
for page in range(2, total_pages + 1):
params["page"] = page
current_url = f"{base_url}?{urllib.parse.urlencode(params)}"
current_html = send_request(current_url, headers)
if current_html:
current_products = parse_product_list(current_html)
all_products.extend(current_products)
print(f"第 {page} 页爬取完成,累计商品数:{len(all_products)}")
else:
print(f"第 {page} 页爬取失败,跳过")
return all_products# 批量爬取前 3 页数据all_products = batch_crawl(keyword, price_from, price_to, sort_type, max_pages=3)print(f"批量爬取完成,总商品数:{len(all_products)}")3.5 步骤 5:数据导出与存储
import pandas as pddef export_products(products, filename="南网商城商品列表.csv"):
# 转换为 DataFrame
df = pd.DataFrame(products)
# 导出 CSV(index=False 表示不保留行索引)
df.to_csv(filename, index=False, encoding="utf-8-sig")
print(f"数据已导出至:{filename}")# 导出数据if all_products:
export_products(all_products)四、进阶优化:稳定性、效率与数据质量
4.1 稳定性优化
异常重试机制:对请求失败(如超时、403)的页面,添加 2-3 次重试逻辑(使用
tenacity库)python运行from tenacity import retry, stop_after_attempt, wait_exponential@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=5))def send_request_with_retry(url, headers): # 同步骤 3.2.1 中的 send_request 逻辑
断点续爬:将已爬取的商品 ID 存储在本地(如 Redis、文本文件),重启时跳过已爬取数据
日志记录:使用
logging库记录爬取过程(请求状态、错误信息、数据量),便于问题排查python运行import logging logging.basicConfig(filename="crawl_log.log", level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")logging.info(f"第 {page} 页爬取完成,提取商品数:{len(products)}")logging.error(f"第 {page} 页爬取失败:{str(e)}")
4.2 效率优化
异步请求:使用 aiohttp 替代 requests,并发处理多页请求(需控制并发数,避免触发反爬)
python运行import aiohttpimport asyncioasync def async_send_request(session, url, headers): await asyncio.sleep(random.uniform(1.5, 3.5)) async with session.get(url, headers=headers, timeout=10) as response: if response.status == 200: return await response.text() else: logging.error(f"异步请求失败,状态码:{response.status},URL:{url}") return None# 异步批量请求async def async_batch_crawl(urls, headers): async with aiohttp.ClientSession() as session: tasks = [async_send_request(session, url, headers) for url in urls] results = await asyncio.gather(*tasks) return results数据缓存:对高频搜索关键词(如 “高压电缆”)的结果进行本地缓存(如使用 Redis),短期内重复请求直接返回缓存数据
增量爬取:记录上次爬取的最大商品 ID 或最新上架时间,下次仅爬取新增数据
4.3 数据质量优化
字段校验与清洗:对价格、起订量等数值字段进行格式校验,剔除异常值(如价格为 0 或过大)
python运行# 价格清洗示例(保留合法数字)def clean_price(price_str): if not price_str: return None match = re.search(r"\d+\.?\d*", price_str) return float(match.group()) if match else None
去重处理:基于商品 ID(item_id)去重,避免分页重复或筛选条件导致的重复数据
缺失值处理:对缺失的核心字段(如价格、供应商)标记为 “未知”,便于后续数据筛选
标准化处理:将执行标准、类目名称等字段标准化(如 “GB/T 12706” 统一为 “GB/T12706”)
五、合规与风控:避免违规与封禁
5.1 合规性要求
数据用途合规:仅用于企业内部采购分析、供应链调研等合法场景,不得用于商业售卖、恶意竞争等违规行为
尊重平台规则:遵守南网商城《用户服务协议》,不得过度爬取影响平台服务器稳定
隐私保护:对供应商联系方式、企业资质等敏感信息,不得非法泄露或滥用
版权声明:若数据用于公开报告或第三方共享,需注明数据来源为南网商城,不得篡改数据真实性
5.2 风控避坑指南
控制请求频率:单 IP 单日请求量不超过 1000 次,单次请求间隔不低于 1.5 秒,避免集中时段(如 9:00-11:00 采购高峰)高频访问
避免账号关联:不同爬虫任务使用不同 Cookie 与代理 IP,避免多个账号共用同一 IP
动态调整策略:定期检查页面结构(如类名、标签变化),及时更新解析规则;若遇到验证码拦截,暂停爬取 1-2 小时后更换 IP 重试
拒绝深度爬取:不爬取平台非公开数据(如未上架商品、供应商后台数据),不模拟登录后进行批量下单、收藏等操作
六、常见问题与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 响应 403 Forbidden | IP 被封锁或 Cookie 失效 | 更换代理 IP、重新登录更新 Cookie、降低请求频率 |
| 提取数据为空 | 页面结构变化或筛选条件无结果 | 重新通过 F12 检查字段路径、调整筛选参数 |
| 部分字段缺失(如价格) | 未登录或未完成实名认证 | 完成企业实名认证,更新登录态 Cookie |
| 请求超时频繁 | 代理 IP 不稳定或网络问题 | 更换高质量代理、增加超时时间(如 15 秒) |
| 分页爬取重复数据 | 分页参数失效或页面缓存 | 每次请求添加随机参数(如 &t = 时间戳)、基于 item_id 去重 |
七、总结与进阶方向
进阶学习方向
智能筛选扩展:基于 Selenium 模拟下拉筛选(如 “供货周期”“质保期”),覆盖更多筛选场景
数据关联分析:结合 item_get 接口(商品详情解析),关联商品列表与技术参数、供应商资质的全量数据
可视化监控:使用 Flask + ECharts 构建商品价格、品类分布的可视化 dashboard,实现实时监控
自动化运维:部署定时爬虫(如每日凌晨爬取),通过邮件 / 企业微信推送新增商品与价格波动预警