在电商数据分析、竞品调研等场景下,获取京东商品信息是很常见的需求。手动一个个去查看、记录商品数据效率低下,于是我开发了这款带图形界面的京东商品爬虫工具,能便捷地爬取指定商品、指定页数的相关数据并保存。
工具功能与技术栈
功能亮点
- 图形化界面操作:无需编写代码,通过简单的输入和点击按钮,就能完成商品数据爬取任务。
- 多参数自定义:可指定要爬取的商品名称,以及爬取的页数(范围 1 - 100 页)。
- 实时进度反馈:在界面上能实时看到爬取进度、商品提取情况以及错误信息等。
- 数据持久化:将爬取到的商品数据(包括 SKU、链接、标题、价格等多维度信息)保存到 Excel 文件,方便后续分析。
- 多线程处理:采用多线程技术,爬取过程不阻塞 GUI 界面,可随时停止爬取任务。
技术栈
- 界面开发:使用
tkinter
库构建图形化界面,这是 Python 内置的 GUI 库,简单易用。 - 网页交互与数据提取:借助
DrissionPage
库操作 Chromium 浏览器,实现网页的加载、元素查找与数据提取。 - 数据处理与存储:利用
pandas
进行数据处理,结合openpyxl
实现 Excel 文件的读写操作。 - 多线程:通过
threading
模块实现多线程,避免爬取过程中界面卡顿。
核心代码解析
界面构建
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)
# 商品名称输入区域
ttk.Label(main_frame, text="商品名称:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.product_entry = ttk.Entry(main_frame, width=50)
self.product_entry.grid(row=0, column=1, sticky=tk.W, pady=5)
self.product_entry.insert(0, "小米") # 默认值
# 爬取页数设置区域
ttk.Label(main_frame, text="爬取页数:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.page_frame = ttk.Frame(main_frame)
self.page_frame.grid(row=1, column=1, sticky=tk.W, pady=5)
self.page_entry = ttk.Entry(self.page_frame, width=10)
self.page_entry.pack(side=tk.LEFT)
self.page_entry.insert(0, "10") # 默认值
ttk.Label(self.page_frame, text="页 (最多100页)").pack(side=tk.LEFT, padx=5)
# 按钮区域
self.button_frame = ttk.Frame(main_frame)
self.button_frame.grid(row=2, column=0, columnspan=2, pady=10)
self.start_btn = ttk.Button(self.button_frame, text="开始爬取", command=self.start_scraping)
self.start_btn.pack(side=tk.LEFT, padx=5)
self.stop_btn = ttk.Button(self.button_frame, text="停止爬取", command=self.stop_scraping, state=tk.DISABLED)
self.stop_btn.pack(side=tk.LEFT, padx=5)
# 进度显示区域
ttk.Label(main_frame, text="爬取进度:").grid(row=3, column=0, columnspan=2, sticky=tk.W, pady=5)
self.progress_text = scrolledtext.ScrolledText(main_frame, wrap=tk.WORD, height=20)
self.progress_text.grid(row=4, column=0, columnspan=2, sticky=tk.NSEW, pady=5)
self.progress_text.config(state=tk.DISABLED)
# 设置网格权重,使文本区域可伸缩
main_frame.grid_rowconfigure(4, weight=1)
main_frame.grid_columnconfigure(1, weight=1)
这段代码通过 tkinter
的布局管理器(grid
和 pack
),搭建了包含商品名称输入框、爬取页数输入框、开始 / 停止按钮以及进度显示文本框的界面,并且对界面组件的样式(如字体)进行了设置,确保中文显示正常。
爬取控制与多线程
def start_scraping(self):
product_name = self.product_entry.get().strip()
page_str = self.page_entry.get().strip()
# 验证输入
if not product_name:
messagebox.showerror("输入错误", "请输入商品名称")
return
try:
num_pages = int(page_str)
if num_pages <= 0 or num_pages > 100:
messagebox.showerror("输入错误", "请输入1-100之间的页数")
return
except ValueError:
messagebox.showerror("输入错误", "请输入有效的页数")
return
# 更新按钮状态
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
self.scraping = True
# 清空日志
self.progress_text.config(state=tk.NORMAL)
self.progress_text.delete(1.0, tk.END)
self.progress_text.config(state=tk.DISABLED)
# 在新线程中开始爬取,避免界面卡顿
threading.Thread(target=self.scrape_jd, args=(product_name, num_pages), daemon=True).start()
start_scraping
方法先对用户输入的商品名称和爬取页数进行验证,确保输入合法。然后调整按钮状态,最后创建新线程执行核心爬取方法 scrape_jd
,这样爬取过程在后台线程进行,不会导致 GUI 界面卡死,提升了用户体验。
商品数据爬取与保存
def scrape_jd(self, shop_name, num_pages=10, batch_size=20):
# 初始化存储数据的列表
all_data = []
# 生成带时间戳的文件名
timestamp = time.strftime("%Y%m%d_%H%M%S")
filename = f"京东_{shop_name}_商品数据_{timestamp}.xlsx"
first_save = True # 标记是否是第一次保存
try:
# 初始化浏览器页面
dp = ChromiumPage()
dp.get('https://www.jd.com/')
self.log("已打开京东首页")
# 搜索商品(等待搜索框加载完成再输入)
try:
search_box = dp.ele('css:.text', timeout=10)
search_box.input(shop_name)
self.log(f"已搜索: {shop_name}")
time.sleep(1) # 等待搜索结果加载
except Exception as e:
self.log(f"搜索失败: {str(e)}")
self.cleanup(dp)
return
for page in range(1, num_pages + 1):
# 检查是否需要停止
if not self.scraping:
break
self.log(f"\n===== 正在爬取第 {page} 页 =====")
page_data = [] # 存储当前页数据
# 等待商品列表加载完成(最多等5秒)
try:
dp.ele('css:._wrapper_2xp6d_3.plugin_goodsCardWrapper', timeout=5)
except:
self.log("当前页商品列表加载失败,跳过该页")
break
# 只在页面内容未完全加载时滚动
if page == 1:
dp.scroll.to_bottom()
time.sleep(0.5)
# 获取当前页所有商品
lis = dp.eles('css:._wrapper_2xp6d_3.plugin_goodsCardWrapper')
if not lis:
self.log("未找到商品,停止爬取")
break
for index, li in enumerate(lis, 1):
# 检查是否需要停止
if not self.scraping:
break
try:
# 1. 商品SKU
sku = li.attr('data-sku') or ""
# 2. 商品链接
product_url = f"https://item.jd.com/{sku}.html" if sku else ""
# 3. 商品标题
title_elem = li.ele('css:._goods_title_container_1x4i2_1 span', timeout=1)
title = title_elem.text.strip() if title_elem else ""
# 4. 商品图片链接
img_elem = li.ele('css:._img_18s24_1', timeout=1)
img_url = img_elem.attr('src') or img_elem.attr('data-src') or ""
img_url = f"https:{img_url}" if img_url and not img_url.startswith('http') else img_url
# 5. 价格信息
price = li.ele('css:._price_1tn4o_13', timeout=1).text.strip() if li.ele('css:._price_1tn4o_13',
timeout=1) else ""
original_price = li.ele('css:._gray_1tn4o_48', timeout=1).text.strip() if li.ele(
'css:._gray_1tn4o_48',
timeout=1) else ""
# 6. 销量信息
sales = li.ele('css:._goods_volume_1xkku_1 span', timeout=1).text.strip() if li.ele(
'css:._goods_volume_1xkku_1 span', timeout=1) else ""
# 7. 店铺信息
shop_name_elem = li.ele('css:._name_d19t5_35 span', timeout=1)
shop_name_text = shop_name_elem.text.strip() if shop_name_elem else ""
shop_url_elem = li.ele('css:._name_d19t5_35', timeout=1)
shop_url = shop_url_elem.attr('href') if shop_url_elem else ""
shop_url = f"https:{shop_url}" if shop_url and not shop_url.startswith('http') else shop_url
# 8. 服务标签
tag = li.ele('css:._textTag_1qbwk_10 span', timeout=1).text.strip() if li.ele(
'css:._textTag_1qbwk_10 span', timeout=1) else ""
# 9. 客服链接
service_url_elem = li.ele('css:._customer_service_icon_d19t5_14', timeout=1)
service_url = service_url_elem.attr('href') if service_url_elem else ""
service_url = f"https:{service_url}" if service_url and not service_url.startswith(
'http') else service_url
# 添加到当前页数据列表
page_data.append({
'SKU': sku,
'商品链接': product_url,
'标题': title,
'图片链接': img_url,
'到手价': price,
'原价': original_price,
'销量': sales,
'店铺名称': shop_name_text,
'店铺链接': shop_url,
'服务标签': tag,
'客服链接': service_url,
'爬取页码': page
})
# 每积累一定数量商品打印一次进度
if index % batch_size == 0:
self.log(f"第 {page} 页已爬取 {index} 个商品")
except Exception as e:
self.log(f"商品 {index} 提取失败: {str(e)[:50]}")
# 批量添加当前页数据到总列表
all_data.extend(page_data)
self.log(f"第 {page} 页爬取完成,共 {len(page_data)} 个商品")
# 写入数据到Excel
try:
df = pd.DataFrame(page_data)
if first_save:
df.to_excel(filename, index=False, engine='openpyxl')
first_save = False
else:
with pd.ExcelWriter(filename, engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer:
startrow = writer.sheets['Sheet1'].max_row
df.to_excel(writer, index=False, header=False, startrow=startrow)
self.log(f"第 {page} 页数据已写入文件")
except Exception as e:
self.log(f"写入Excel失败: {str(e)}")
# 翻页
if page < num_pages and self.scraping:
try:
next_btn = dp.ele('css:._pagination_next_1jczn_8', timeout=3)
next_btn.click()
time.sleep(1)
except Exception as e:
self.log(f"无法翻到下一页: {e}")
break
# 爬取完成
self.log(f"\n爬取完成,共获取 {len(all_data)} 条记录")
self.log(f"数据已保存到: {filename}")
finally:
# 清理资源
try:
dp.quit()
except:
pass
self.cleanup()
scrape_jd
方法是核心爬取逻辑:
- 首先初始化浏览器,打开京东首页并搜索指定商品。
- 然后逐页爬取商品数据,通过
DrissionPage
的元素查找方法,提取商品的 SKU、链接、标题、价格等信息,存储到列表中。 - 每爬取完一页,就使用
pandas
将当前页数据写入 Excel 文件,支持增量写入,避免数据丢失。 - 爬取过程中还会处理翻页逻辑,以及异常情况,确保爬取任务尽可能顺利完成。
使用方法
- 运行代码后,会弹出图形界面窗口。
- 在 “商品名称” 输入框中输入要爬取的商品名称(如 “小米”)。
- 在 “爬取页数” 输入框中输入要爬取的页数(1 - 100 之间的整数)。
- 点击 “开始爬取” 按钮,开始爬取任务,进度会实时显示在下方文本框中。
- 若要中途停止爬取,点击 “停止爬取” 按钮即可。
- 爬取完成后,数据会保存为 Excel 文件,文件名包含商品名称和时间戳,方便识别。
资料获取
私信老师