ARTICLE DETAIL

资讯详情

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

Python爬虫实战:构建高效京东商品数据采集系统

Python爬虫实战:构建高效京东商品数据采集系统 简介这是一份面向Python初学者与爬虫实践者的轻量级京东商品价格监控工具解决个人用户实时追踪京东优惠、辅助购物决策的实际需求。资源为单文件Python爬虫脚本.py压缩包仅含1个源码文件大小仅2KB结构简洁便于快速理解核心逻辑与调试修改。已有420人学习下载适合希望掌握requestsBeautifulSoup基础抓取、动态内容应对思路及邮件通知集成的入门者。读者可直接运行脚本实现商品页价格提取代码内嵌了HTTP请求、HTML解析、SMTP邮件发送等关键模块同时体现异常重试与基础反爬规避意识是学习电商爬虫全流程的典型微型案例。1. 项目缘起为什么我们需要一个“京东爬虫”如果你做过电商数据分析、价格监控或者竞品调研那你一定对“爬虫”这个词不陌生。而“京东爬虫”更是一个在数据圈里高频出现的需求。我自己最初接触这个需求是为了给公司做一款智能比价工具。市面上虽然有一些现成的数据服务但要么价格昂贵要么数据维度不全要么更新频率跟不上。更重要的是当你想根据自己特定的业务逻辑比如只抓取某个三级类目下、好评率超过95%、且近期有降价趋势的商品去定制化采集数据时通用API就显得力不从心了。于是自己动手写一个爬虫就成了最直接、也最可控的方案。Python凭借其丰富的生态库如requests,BeautifulSoup,Scrapy和相对简单的语法自然成了首选。这个项目我称之为JDspider它的核心目标就是稳定、高效、可定制地从京东网站抓取商品、价格、评论等关键信息并将其结构化存储供后续分析使用。听起来简单但真正做起来从反爬对抗到数据清洗每一步都藏着不少“坑”。接下来我就结合自己踩过的这些坑把这个项目的实现思路、核心代码和避坑经验完整地分享出来。2. 环境搭建与核心库选型不只是安装Python那么简单很多人觉得爬虫入门就是pip install requests然后写几行代码就能跑了。对于简单的静态页面或许可以但面对京东这样防护严密的大型电商网站这种想法就太天真了。环境搭建是地基选型决定了你未来能盖多高的楼。2.1 Python版本与虚拟环境首先我强烈建议使用Python 3.8及以上版本。新版本在异步支持、性能优化上更好而且主流库的兼容性也最强。别再用Python 2.7了很多新库已不再支持。第二步务必使用虚拟环境。这能避免项目间的库版本冲突。我习惯用venv这是Python 3自带的无需额外安装。# 创建项目目录并进入 mkdir jd_spider_project cd jd_spider_project # 创建虚拟环境 python3 -m venv venv # 激活虚拟环境 (Linux/macOS) source venv/bin/activate # 激活虚拟环境 (Windows) venv\Scripts\activate激活后你的命令行提示符前会出现(venv)字样这表示你正工作在独立的Python环境中。2.2 核心库的“四驾马车”对于京东爬虫我主要依赖四个核心库它们各有分工requests requests-html (网络请求)requests是发起HTTP请求的绝对主力简单易用。但京东页面大量使用JavaScript渲染光靠requests拿到的是没有数据的“空壳”。requests-html这是requests作者开发的另一个库它内置了一个简易的浏览器内核可以执行页面中的JS从而获取渲染后的完整HTML。这是应对京东动态页面的关键。安装它pip install requests-html。BeautifulSoup4 lxml (HTML解析)拿到HTML后我们需要从中提取数据如商品标题、价格。BeautifulSoup4(bs4) 提供了非常友好、灵活的API来解析和搜索HTML/XML文档。lxml是一个解析器速度比Python自带的html.parser快很多。BeautifulSoup可以指定使用lxml作为后端引擎。所以通常一起安装pip install beautifulsoup4 lxml。pandas (数据处理与存储)爬下来的数据往往是零散的字典或列表pandas可以轻松地将它们组织成结构化的DataFrame并方便地导出为CSV、Excel或写入数据库。pip install pandas。fake-useragent (请求头伪装)反爬的第一道防线就是检查User-Agent。使用固定的UA很容易被识别。fake-useragent库可以随机生成主流浏览器的UA提升请求的隐蔽性。pip install fake-useragent。一个完整的安装命令如下pip install requests requests-html beautifulsoup4 lxml pandas fake-useragent注意requests-html在首次使用时会自动下载一个Chromium内核用于渲染JS。这个过程可能需要一些时间并且需要稳定的网络环境。如果下载失败可以尝试设置国内镜像源或者寻找其他替代方案如selenium但selenium更重。3. 逆向京东页面寻找数据的“入口”这是爬虫最核心、也最具技术挑战的一步。你不能对着首页胡乱抓取必须找到数据真正的来源。京东的数据加载主要分两种一种是服务器直接返回的HTML多见于列表页另一种是通过AJAX接口返回的JSON数据多见于商品详情、价格、评论。3.1 列表页抓取从搜索词到商品ID假设我们要抓取“笔记本电脑”这个关键词下的商品。首先在浏览器中打开京东搜索“笔记本电脑”观察地址栏URL。 一个典型的URL可能是https://search.jd.com/Search?keyword笔记本电脑encutf-8我们可以用requests-html来模拟这个请求并渲染JS。from requests_html import HTMLSession from fake_useragent import UserAgent ua UserAgent() session HTMLSession() keyword 笔记本电脑 url fhttps://search.jd.com/Search?keyword{keyword}encutf-8 headers { User-Agent: ua.random, Referer: https://www.jd.com/ } try: resp session.get(url, headersheaders, timeout10) # 关键步骤渲染JavaScript等待页面加载完成 resp.html.render(sleep2, scrolldown2) # sleep给JS执行时间scrolldown模拟滚动加载 html_content resp.html.html # 此时html_content包含了JS渲染后的完整商品列表 print(f页面获取成功长度{len(html_content)}) except Exception as e: print(f请求页面失败{e})拿到渲染后的HTML后我们需要分析页面结构找到商品列表的容器和每个商品项的标签。按F12打开开发者工具使用元素选择器CtrlShiftC点击一个商品你会发现商品列表通常在一个div idJ_goodsList的容器内每个商品项是li classgl-item。接下来用BeautifulSoup解析并提取商品IDSKU和基础信息。from bs4 import BeautifulSoup import re soup BeautifulSoup(html_content, lxml) goods_list soup.find(div, idJ_goodsList) if goods_list: items goods_list.find_all(li, class_gl-item) sku_list [] for item in items: # 获取商品SKU (通常藏在data-sku属性里) sku item.get(data-sku) if not sku: # 另一种可能从商品链接中提取 link_tag item.find(a, hrefre.compile(r//item.jd.com/\d\.html)) if link_tag: href link_tag.get(href) sku re.search(r//item.jd.com/(\d)\.html, href).group(1) if sku: # 提取商品标题、价格列表页价格可能不是实时价、店铺等 title_tag item.find(div, class_p-name) title title_tag.em.get_text(stripTrue) if title_tag else N/A price_tag item.find(div, class_p-price) price price_tag.strong.i.get_text(stripTrue) if price_tag else N/A sku_list.append({sku: sku, title: title, list_price: price}) print(f发现商品{sku} - {title[:30]}... - 列表价{price}) print(f共发现 {len(sku_list)} 个商品)为什么先抓SKU因为SKU是京东商品的唯一标识。有了SKU我们就可以构造出商品详情页的URLhttps://item.jd.com/{SKU}.html以及更重要的——数据接口的URL。3.2 挖掘核心数据接口价格、库存、促销列表页的信息有限实时价格、库存状态、促销信息等关键数据通常通过AJAX接口动态加载。这是爬虫的难点也是重点。再次打开开发者工具切换到Network网络选项卡然后刷新一个商品详情页如https://item.jd.com/100012014774.html。在瀑布流中筛选XHR或Fetch类型的请求。你会看到很多以.js?或.html?结尾的请求其中往往就藏着我们需要的数据。经过分析京东商品的核心数据接口通常有这几个规律价格接口https://p.3.cn/prices/mgets?skuIdsJ_{SKU}或https://item-soa.jd.com/getWareBusiness?skuId{SKU}。返回JSON格式的实时价格、促销价等信息。商品详情接口https://cd.jd.com/description/channel?skuId{SKU}cdn2。返回商品描述信息。评论接口https://club.jd.com/comment/productPageComments.action?productId{SKU}score0sortType5page{page}pageSize10。用于分页获取评论。以价格接口为例我们如何调用它import requests import json def get_real_price(sku_id): 获取商品实时价格 price_url fhttps://p.3.cn/prices/mgets?skuIdsJ_{sku_id} headers { User-Agent: ua.random, Referer: fhttps://item.jd.com/{sku_id}.html # Referer很重要模拟从商品页跳转过来 } try: resp requests.get(price_url, headersheaders, timeout5) resp.raise_for_status() # 检查HTTP错误 data resp.json() if data and len(data) 0: price_info data[0] # p: 定价 op: 原价 m: 促销价 current_price price_info.get(p, N/A) original_price price_info.get(op, N/A) return {current_price: current_price, original_price: original_price} except requests.exceptions.RequestException as e: print(f获取价格失败SKU: {sku_id}: {e}) except json.JSONDecodeError as e: print(f解析价格JSON失败SKU: {sku_id}: {e}) return None # 使用示例 sku 100012014774 price_data get_real_price(sku) if price_data: print(f商品 {sku} 实时价{price_data[current_price]}, 原价{price_data[original_price]})实操心得寻找接口没有捷径必须耐心地使用浏览器的开发者工具观察页面加载过程中发了哪些请求哪个请求的Preview或Response里包含了你要的数据。重点关注XHR/Fetch请求它们的Query String Parameters查询参数和Request Headers请求头需要被完整地模拟到你的爬虫代码中。Referer和Cookie是其中两个非常关键的头部字段。4. 构建健壮的爬虫架构从单次请求到批量任务有了提取单个商品数据的能力我们需要将其工程化构建一个能处理批量任务、具备错误处理和日志记录的爬虫系统。4.1 设计数据流与存储一个简单的爬虫数据流可以这样设计1. 输入种子关键词/分类ID - 2. 抓取列表页获取SKU列表 - 3. 遍历SKU列表 - 4. 并发请求详情/价格接口 - 5. 解析并清洗数据 - 6. 存储到文件或数据库存储方面对于中小规模数据CSV或JSON文件简单方便。对于大规模或需要频繁查询的数据建议使用数据库如SQLite轻量、MySQL或PostgreSQL。这里我们用pandas将数据暂存为CSV。import pandas as pd import time from concurrent.futures import ThreadPoolExecutor, as_completed class JDSpider: def __init__(self): self.session HTMLSession() self.ua UserAgent() self.data_list [] # 临时存储爬取结果 self.headers_template { User-Agent: , Referer: https://www.jd.com/, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, } def get_page_with_js(self, url): 渲染JS获取页面 headers self.headers_template.copy() headers[User-Agent] self.ua.random try: resp self.session.get(url, headersheaders, timeout15) resp.html.render(sleep1.5, timeout20) return resp.html.html except Exception as e: print(f渲染页面失败 {url}: {e}) return None def parse_list_page(self, html): 解析列表页提取SKU # 复用之前的解析代码返回sku列表 soup BeautifulSoup(html, lxml) # ... 解析逻辑 ... return sku_list def fetch_product_detail(self, sku): 获取单个商品的完整详情 detail {sku: sku} # 1. 获取价格 price_info get_real_price(sku) # 调用之前定义的函数 if price_info: detail.update(price_info) # 2. 获取商品标题等信息可从列表页已获取或再请求详情页 # 3. 可以在这里添加获取评论、规格等更多信息的函数调用 # ... # 模拟一个网络延迟 time.sleep(0.5) return detail def crawl_by_keyword(self, keyword, max_pages1): 根据关键词爬取 all_skus [] for page in range(1, max_pages1): # 京东列表页URL有规律page参数通常是奇数 1,3,5... jd_page page * 2 - 1 list_url fhttps://search.jd.com/Search?keyword{keyword}page{jd_page} print(f正在抓取列表页: {list_url}) html self.get_page_with_js(list_url) if html: skus self.parse_list_page(html) all_skus.extend(skus) print(f第{page}页找到 {len(skus)} 个商品) time.sleep(2) # 礼貌性延迟避免请求过快 print(f开始获取 {len(all_skus)} 个商品的详情...) # 使用线程池并发抓取详情提高效率 with ThreadPoolExecutor(max_workers5) as executor: # 控制并发数别太高 future_to_sku {executor.submit(self.fetch_product_detail, item[sku]): item for item in all_skus} for future in as_completed(future_to_sku): sku_info future_to_sku[future] try: detail future.result() if detail: self.data_list.append(detail) print(f成功获取: {detail.get(sku)} - 价格: {detail.get(current_price)}) except Exception as e: print(f获取商品 {sku_info[sku]} 详情时出错: {e}) def save_to_csv(self, filenamejd_products.csv): 保存数据到CSV if self.data_list: df pd.DataFrame(self.data_list) # 选择要保存的列并排序 columns_order [sku, title, current_price, original_price, ...] df df.reindex(columns[col for col in columns_order if col in df.columns]) df.to_csv(filename, indexFalse, encodingutf-8-sig) # utf-8-sig解决Excel中文乱码 print(f数据已保存至 {filename}, 共 {len(df)} 条记录。) else: print(没有数据可保存。) # 使用示例 if __name__ __main__: spider JDSpider() spider.crawl_by_keyword(蓝牙耳机, max_pages2) # 抓取2页 spider.save_to_csv()4.2 并发控制与反爬策略上面的代码使用了ThreadPoolExecutor进行并发请求这能极大提升抓取效率。但并发是一把双刃剑过高的并发请求会迅速触发京东的反爬机制返回验证码、封锁IP。核心策略控制并发数max_workers建议设置在3-10之间根据你的网络环境和目标服务器的容忍度调整。可以先从3开始测试。添加随机延迟在请求之间插入随机的等待时间模拟人类操作。可以用time.sleep(random.uniform(1, 3))。使用代理IP池这是应对IP封锁最有效的方法。当请求失败或收到验证码时自动切换下一个代理IP。你可以购买付费代理服务或者寻找免费的代理IP源但免费代理通常不稳定。维护Cookie池对于一些需要登录状态才能访问的数据需要维护一组有效的Cookie并轮换使用。设置合理的请求头除了User-AgentAccept、Accept-Language、Referer等头部都要模拟得像一个真实浏览器。处理验证码如果遇到验证码简单的方案是暂停一段时间或更换IP。复杂的方案需要引入图像识别如OCR但这会大大增加复杂度。import random import time def safe_request(url, headers, proxiesNone, retry3): 一个带有重试和延迟的安全请求函数 for i in range(retry): try: # 随机延迟 time.sleep(random.uniform(0.5, 2.0)) resp requests.get(url, headersheaders, proxiesproxies, timeout10) # 检查是否被反爬例如返回了验证码页面 if 验证码 in resp.text or resp.status_code 403: print(f请求可能被拦截状态码{resp.status_code}) if proxies: print(f考虑切换代理...) # 触发重试或更换代理逻辑 raise Exception(Anti-spider detected) resp.raise_for_status() return resp except Exception as e: print(f请求失败第{i1}次重试: {e}) if i retry - 1: raise time.sleep(2 ** i) # 指数退避延迟 return None5. 数据清洗与结构化从原始数据到可用信息爬取下来的数据往往是杂乱无章的包含HTML标签、多余的空格、不一致的格式等。直接存储这样的数据价值很低必须进行清洗。5.1 常见的清洗任务文本清理去除商品标题、描述中的多余空格、换行符、HTML标签。import re from bs4 import BeautifulSoup def clean_text(text): if not text: return # 去除HTML标签 soup BeautifulSoup(text, html.parser) text soup.get_text() # 合并多个空白字符为一个空格 text re.sub(r\s, , text) # 去除首尾空格 text text.strip() return text dirty_text span 华为笔记本\nMateBook emX Pro/em /span clean clean_text(dirty_text) # 输出华为笔记本 MateBook X Pro价格格式化价格字段可能是字符串¥5,299.00或5299需要统一转换为浮点数。def parse_price(price_str): if not price_str or price_str N/A: return None # 移除货币符号、逗号等非数字字符除了小数点 try: price float(re.sub(r[^\d.], , price_str)) return price except ValueError: return None规格参数提取商品详情页的规格参数通常是一个键值对列表。我们可以将其解析为字典或JSON字符串方便后续分析。# 假设我们从某个接口拿到了规格参数HTML片段 spec_html ulli商品名称华为笔记本/lili系统Windows 11/lili分辨率超高清屏2K/2.5K/3K/4K/li/ul soup BeautifulSoup(spec_html, lxml) spec_dict {} for li in soup.find_all(li): text li.get_text(stripTrue) if in text: key, value text.split(, 1) spec_dict[key.strip()] value.strip() # spec_dict: {商品名称: 华为笔记本, 系统: Windows 11, 分辨率: 超高清屏2K/2.5K/3K/4K}5.2 使用Pandas进行批量清洗当数据量较大时在pandas的DataFrame上进行向量化操作效率更高。def clean_dataframe(df): 清洗整个DataFrame df_clean df.copy() # 1. 清洗文本列 text_columns [title, shop_name] for col in text_columns: if col in df_clean.columns: df_clean[col] df_clean[col].apply(clean_text) # 2. 转换价格列 price_columns [current_price, original_price] for col in price_columns: if col in df_clean.columns: df_clean[col] df_clean[col].apply(parse_price) # 3. 处理缺失值 # 例如用中位数填充价格缺失值需谨慎根据业务逻辑 # df_clean[current_price].fillna(df_clean[current_price].median(), inplaceTrue) # 或者直接删除缺失关键信息的行 df_clean.dropna(subset[sku, title], inplaceTrue) # 4. 去重基于SKU df_clean.drop_duplicates(subset[sku], keepfirst, inplaceTrue) return df_clean # 加载原始数据 df_raw pd.read_csv(jd_raw_data.csv) df_clean clean_dataframe(df_raw) df_clean.to_csv(jd_clean_data.csv, indexFalse)6. 进阶挑战与应对策略爬虫与反爬的攻防随着你爬取频率和规模的增加必然会遇到更严厉的反爬措施。以下是我在实践中总结的一些进阶问题和应对思路。6.1 动态加载与加密参数京东的一些接口特别是涉及用户行为、风控等级较高的如“猜你喜欢”、“实时库存”其请求参数可能是加密的或者需要携带一个动态生成的token。你无法直接通过观察URL构造请求。应对方法逆向JavaScript这是最根本但也最复杂的方法。使用浏览器开发者工具的Sources面板找到生成这些参数的JS文件通过断点调试、代码分析理解其加密逻辑然后用Python如execjs,PyExecJS库复现该逻辑。使用无头浏览器当JS逆向过于复杂时可以退而求其次使用selenium或Playwright这类自动化测试工具直接控制一个真实的浏览器如Chrome去加载页面、执行操作然后从浏览器内存中获取渲染后的数据。这种方法能绕过绝大多数前端加密但代价是速度极慢、资源消耗大不适合大规模爬取。寻找替代接口有时同一个数据可能有多个来源。多花时间在Network里翻找也许能找到参数更简单、防护更弱的“后门”接口。6.2 滑块验证码与行为验证这是目前电商网站最常用的反爬手段之一。当你请求过于频繁时服务器会返回一个需要拖动滑块或点击图中文字的验证码。应对方法降低频率避免触发这是最经济有效的方法。通过设置更长的随机延迟、使用更多的代理IP分散请求将你的请求行为控制在“正常用户”的范围内。第三方打码平台如果必须突破可以考虑接入打码平台如超级鹰、图鉴等。当爬虫遇到验证码时将验证码图片发送给平台平台返回识别结果爬虫再模拟提交。这需要额外成本且平台识别率并非100%。机器学习方案对于简单的滑块验证码可以尝试用OpenCV等库进行图像识别计算滑块缺口位置。但这需要一定的图像处理知识且网站方也会不断升级验证码样式。6.3 数据更新与增量爬取对于价格监控这类需要持续运行的任务全量爬取每次都很耗时。我们需要实现增量爬取只抓取发生变化的数据。实现思路建立本地数据仓库将每次爬取的结果SKU、价格、时间戳存入数据库。制定更新策略定时全量对于数据量不大如几千个SKU且变化频繁的可以每天在低峰期如凌晨全量更新一次。增量更新记录每个SKU的最后更新时间。下次爬取时只请求那些“最后更新时间”距现在超过一定阈值如1小时的商品的价格接口。这需要你有一个存储SKU列表和上次更新时间的表。监听变化更高级的做法是分析京东的价格接口看是否有推送机制如WebSocket但这通常不对外开放。# 一个简单的增量更新逻辑伪代码 import sqlite3 from datetime import datetime, timedelta def incremental_update(sku_list): conn sqlite3.connect(jd_data.db) cursor conn.cursor() # 假设有表 products(sku, price, last_updated) update_threshold datetime.now() - timedelta(hours6) # 6小时前的数据需要更新 skus_to_update [] for sku in sku_list: cursor.execute(SELECT last_updated FROM products WHERE sku ?, (sku,)) row cursor.fetchone() if not row or datetime.fromisoformat(row[0]) update_threshold: skus_to_update.append(sku) # 只爬取 skus_to_update 列表中的商品 # ... conn.close()7. 法律与道德边界合规爬虫指南最后也是最重要的一点我们必须讨论爬虫的合法性。爬虫技术本身是中立的但如何使用它决定了其性质。遵守robots.txt访问https://www.jd.com/robots.txt查看京东允许或禁止爬虫抓取的目录。虽然这不是法律文件但遵守它是行业惯例和尊重对方服务器的表现。通常京东会禁止爬取用户个人数据、订单信息等敏感路径。限制爬取速度与频率你的爬虫行为不应影响目标网站的正常运营。过快的请求相当于DDoS攻击。务必添加延迟控制并发。仅抓取公开数据只抓取无需登录即可访问的公开商品信息、价格、公开评论等。绝对不要尝试爬取用户个人信息、交易数据、商家后台数据等非公开或敏感信息。尊重数据版权与用途抓取的数据用于个人学习、研究或企业内部分析通常是可接受的。但如果用于商业盈利、特别是直接与京东竞争的业务或者大规模公开散发则可能涉及侵权和不正当竞争。注意隐私政策京东的用户协议和隐私政策中可能有关于数据抓取的条款。核心原则将你的爬虫想象成一个“特别有礼貌的、动作慢一点的普通用户”。你的目的是获取数据而不是搞垮对方的服务器。在项目开始前最好能评估一下风险对于重要的商业项目咨询法律人士的意见是明智之举。这个JDspider项目从简单的需求出发逐步深入到网络请求、页面解析、反爬对抗、系统架构和数据处理的方方面面。它不仅仅是一个脚本更是一个需要持续维护和调整的系统。希望我分享的这些思路、代码片段和踩坑经验能帮你少走弯路更高效、更稳定地获取你所需的数据。记住爬虫是一场持久战耐心和不断学习是唯一的捷径。本文还有配套的精品资源点击获取
返回列表