当前位置: 首页 > news >正文

京东商品价格爬虫实战:破解动态加载与反爬机制的完整指南

目录

前言:为什么选择爬取京东价格?

一、技术选型:为什么是这个组合?

1.1 动态加载的解决方案

1.2 完整技术栈

1.3 环境准备

二、破解京东反爬的十层防护

三、完整代码实现

3.1 浏览器配置类

3.2 价格提取器

3.3 批量爬取与代理管理

3.4 防封号策略增强版

3.5 主程序与使用示例

四、踩坑实录与解决方案

4.1 坑一:无头模式被检测

4.2 坑二:chromedriver版本不匹配

4.3 坑三:价格明明在页面上但就是抓不到

4.4 坑四:京东的随机class名

4.5 坑五:触发验证码后被无限重定向

4.6 坑六:Cookie过期后无法获取价格

五、性能优化与分布式扩展

5.1 单机优化

5.2 分布式爬虫架构

六、法律与道德提醒


前言:为什么选择爬取京东价格?

在电商数据分析、价格监控、竞品研究等领域,获取商品价格是最基础也最关键的一步。京东作为中国最大的B2C电商平台之一,其商品数据价值极高。然而,京东采用了典型的动态加载技术,传统的requests+BeautifulSoup方式会直接碰壁——你会发现页面源代码里根本找不到价格数字。

这篇文章将带你从零开始,用2025年最新的爬虫技术,完整实现一个能够稳定爬取京东商品价格的爬虫系统。我会把踩过的坑、遇到的验证码、被封IP的教训都分享出来,争取让你看完就能动手写一个能用的爬虫。

一、技术选型:为什么是这个组合?

1.1 动态加载的解决方案

京东商品页面是这样的:你打开一个商品页,看到一个价格数字,但查看网页源代码搜索这个数字——找不到。价格是通过JavaScript异步请求加载的。传统的requests.get()只能拿到HTML骨架。

解决方案有三种:

  • 分析Ajax接口:最优雅,效率最高,但需要逆向JS逻辑

  • Selenium/Playwright模拟浏览器:最暴力,最稳定,适合初学者

  • Pyppeteer:异步版Puppeteer,介于两者之间

我选择Selenium作为主力。为什么?因为京东的反爬会检测webdriver特征,但我们可以通过参数规避。而Ajax接口京东经常换参数加密方式,维护成本太高。

1.2 完整技术栈

python

selenium==4.15.0 # 浏览器自动化 webdriver-manager==4.0.1 # 自动管理chromedriver pandas==2.1.0 # 数据存储 fake-useragent==1.4.0 # 随机UA retrying==1.3.4 # 重试机制 time, random, re # 标准库

1.3 环境准备

首先得安装Chrome浏览器。然后用pip安装依赖:

bash

pip install selenium webdriver-manager pandas fake-useragent retrying

webdriver-manager是个好东西,它会自动下载匹配你Chrome版本的驱动,不用手动去找了。

二、破解京东反爬的十层防护

在写代码之前,得先理解京东会怎么拦你。我在实际爬取中遇到的障碍包括:

  1. 检测webdriver属性- 京东的JS会检查window.navigator.webdriver是不是true

  2. 请求频率限制- 一秒超过2次就弹验证码

  3. Cookie失效- 不携带有效cookie,返回登录页面

  4. IP黑名单- 短时间内大量请求,IP直接被ban几个小时

  5. User-Agent校验- 非浏览器UA直接拒绝

  6. Referer检查- 空Referer或者异常Referer会被标记

  7. x-requested-with头- Ajax请求缺少这个头

  8. 加密参数- 部分接口需要生成fingerprint

  9. 滑块验证码- 触发频率限制后弹出

  10. 账号风控- 用爬虫程序的cookie登录会被限制

针对这些,我们的应对策略会在代码中一一体现。

三、完整代码实现

3.1 浏览器配置类

这是最关键的部分。我们需要启动一个看起来完全像真实用户的Chrome实例。

python

# config.py import random from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from fake_useragent import UserAgent class ChromeDriverConfig: """配置一个能骗过京东反爬的Chrome浏览器""" @staticmethod def get_driver(headless=False, proxy=None): """ 获取配置好的driver :param headless: 是否无头模式(建议False,因为无头模式更容易被检测) :param proxy: 代理地址,如 "http://127.0.0.1:8080" """ options = Options() # 1. 随机User-Agent,京东会根据UA做初步筛选 ua = UserAgent() random_ua = ua.random options.add_argument(f'user-agent={random_ua}') # 2. 隐藏webdriver特征 - 最重要的一步 # 这个实验性的参数会移除navigator.webdriver属性 options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_experimental_option('useAutomationExtension', False) # 3. 禁用自动化提示条 options.add_argument('--disable-blink-features=AutomationControlled') # 4. 设置窗口大小,避免小窗口被识别为爬虫 options.add_argument('--window-size=1920,1080') # 5. 禁用GPU加速,减少资源占用 options.add_argument('--disable-gpu') # 6. 禁用沙箱模式(Docker环境下需要) options.add_argument('--no-sandbox') # 7. 禁用共享内存(Linux环境下) options.add_argument('--disable-dev-shm-usage') # 8. 屏蔽通知弹窗 options.add_argument('--disable-notifications') # 9. 禁用插件 options.add_argument('--disable-plugins') # 10. 设置语言 options.add_argument('--lang=zh-CN') # 11. 设置默认编码 options.add_argument('--accept-encoding=utf-8') # 12. 忽略证书错误 options.add_argument('--ignore-certificate-errors') # 13. 允许所有Cookie options.add_argument('--enable-features=NetworkService,NetworkServiceInProcess') # 14. 无头模式(可选,但容易被检测) if headless: options.add_argument('--headless=new') # 无头模式下需要额外设置,避免被检测 options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36') # 15. 代理配置 if proxy: options.add_argument(f'--proxy-server={proxy}') # 16. 常用预制选项,使浏览器更像真实用户 preferences = { 'profile.default_content_setting_values.notifications': 2, # 禁用通知 'credentials_enable_service': False, # 禁用保存密码 'profile.password_manager_enabled': False, # 禁用密码管理器 'profile.default_content_settings.popups': 0, # 禁止弹窗 'download.prompt_for_download': False, # 自动下载 'download.default_directory': '/dev/null', # 下载目录 } options.add_experimental_option('prefs', preferences) # 创建service,使用webdriver-manager自动管理驱动 service = Service(ChromeDriverManager().install()) # 创建driver driver = webdriver.Chrome(service=service, options=options) # 执行脚本来隐藏webdriver痕迹(另一种方式) driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] }); Object.defineProperty(navigator, 'languages', { get: () => ['zh-CN', 'zh', 'en'] }); window.chrome = { runtime: {} }; ''' }) return driver

3.2 价格提取器

价格在页面里藏得很深。京东的价格有两种呈现方式:

  • 普通价格:直接在某个spandiv

  • 促销价格:有时需要触发价格组件才能加载

python

# price_extractor.py import re import time from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException class JDPriceExtractor: """京东商品价格提取器""" def __init__(self, driver, timeout=10): self.driver = driver self.timeout = timeout self.wait = WebDriverWait(driver, timeout) def extract_price_from_detail_page(self, sku_id): """ 从商品详情页提取价格 :param sku_id: 京东商品ID :return: dict包含价格信息 """ url = f'https://item.jd.com/{sku_id}.html' self.driver.get(url) # 随机延迟,模拟人类行为 time.sleep(random.uniform(2, 4)) result = { 'sku_id': sku_id, 'url': url, 'price': None, 'original_price': None, 'promotion_info': None, 'success': False } try: # 方法1:等待价格元素出现 - 最常见的价格位置 price_selectors = [ '#price > div > div > span.price', # 新版页面 '#jd-price', # 旧版页面 '.price.J-p-{}'.format(sku_id), # 动态类名 'span.p-price', # 促销价格 'div.summary-price > div > div > span', # 总结区域价格 'div.sku-price > span' # SKU价格 ] price_element = None for selector in price_selectors: try: price_element = self.wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, selector)) ) if price_element and price_element.text.strip(): break except: continue if price_element: price_text = price_element.text.strip() # 提取数字,处理像"¥199.00"或"199.00"这样的格式 price_match = re.search(r'[\d,]+\.?\d*', price_text) if price_match: result['price'] = float(price_match.group().replace(',', '')) # 方法2:获取原价(划线价) original_price_selectors = [ 'del.J-p-{}'.format(sku_id), 'span.price.J-price', 'div.summary-price > div > del', 's.price' ] for selector in original_price_selectors: try: orig_element = self.driver.find_element(By.CSS_SELECTOR, selector) if orig_element and orig_element.text.strip(): price_text = orig_element.text.strip() price_match = re.search(r'[\d,]+\.?\d*', price_text) if price_match: result['original_price'] = float(price_match.group().replace(',', '')) break except: continue # 方法3:检测促销信息(满减、优惠券等) try: promotion_selectors = [ 'div.promo-tag', 'div.promo-tag-list', 'div.discount-wrap', 'div.j-quantity-fake-discount' ] promo_elements = [] for selector in promotion_selectors: promo_elements.extend(self.driver.find_elements(By.CSS_SELECTOR, selector)) if promo_elements: result['promotion_info'] = [elem.text.strip() for elem in promo_elements if elem.text.strip()] except: pass # 判断是否成功 if result['price']: result['success'] = True else: # 可能遇到验证码页面 if self._check_captcha_page(): result['success'] = False result['error'] = 'Captcha detected' else: result['success'] = False result['error'] = 'Price not found' except TimeoutException: result['error'] = 'Timeout waiting for price element' except Exception as e: result['error'] = str(e) return result def extract_price_from_search_page(self, sku_id): """ 另一种方法:通过搜索页面获取价格 有时候详情页反爬太强,但搜索页还能抓 """ search_url = f'https://search.jd.com/Search?keyword={sku_id}' self.driver.get(search_url) time.sleep(random.uniform(2, 3)) try: # 搜索页的价格通常在li元素里 product_items = self.wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'li.gl-item')) ) for item in product_items: # 检查SKU是否匹配 try: sku_elem = item.find_element(By.CSS_SELECTOR, 'div[data-sku]') item_sku = sku_elem.get_attribute('data-sku') if item_sku == sku_id: price_elem = item.find_element(By.CSS_SELECTOR, 'div.p-price i.price') price_text = price_elem.text.strip() price_match = re.search(r'[\d,]+\.?\d*', price_text) if price_match: return float(price_match.group().replace(',', '')) except: continue except: pass return None def _check_captcha_page(self): """检查是否遇到了验证码页面""" page_source = self.driver.page_source captcha_keywords = ['captcha', '验证码', '滑块', 'verify'] for keyword in captcha_keywords: if keyword in page_source.lower(): return True return False def get_price_via_ajax(self, sku_id): """ 终极方法:直接请求价格接口 这个方法需要分析京东的前端接口,这里给出思路 """ # 京东的价格接口示例(注意:这个接口随时可能变) # 通常格式:https://p.3.cn/prices/mgets?skuIds=J_{sku_id} import requests api_url = f'https://p.3.cn/prices/mgets?skuIds=J_{sku_id}' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': f'https://item.jd.com/{sku_id}.html', 'Accept': 'application/json, text/plain, */*' } try: response = requests.get(api_url, headers=headers, timeout=5) if response.status_code == 200: data = response.json() if data and 'p' in data[0]: return float(data[0]['p']) except: pass return None

3.3 批量爬取与代理管理

实际应用中,我们往往需要爬取成百上千个商品。这时候单线程+单IP必死无疑。

python

# batch_crawler.py import random import time import json import pandas as pd from datetime import datetime from concurrent.futures import ThreadPoolExecutor, as_completed from retrying import retry from selenium.webdriver.common.by import By class JDBatchCrawler: """京东批量价格爬虫""" def __init__(self, driver_config, max_workers=3, request_interval=3): """ :param driver_config: ChromeDriverConfig类 :param max_workers: 并发数(不要太高,京东会封) :param request_interval: 请求间隔秒数 """ self.driver_config = driver_config self.max_workers = max_workers self.request_interval = request_interval self.results = [] self.failed_skus = [] @retry(stop_max_attempt_number=3, wait_fixed=2000) def crawl_single_sku(self, sku_id, proxy=None): """ 爬取单个商品,带重试机制 """ driver = None try: driver = self.driver_config.get_driver(proxy=proxy) extractor = JDPriceExtractor(driver) result = extractor.extract_price_from_detail_page(sku_id) # 添加时间戳 result['crawl_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 随机延时,避免频率过高 time.sleep(random.uniform(1, self.request_interval)) return result except Exception as e: print(f'Error crawling {sku_id}: {str(e)}') return { 'sku_id': sku_id, 'success': False, 'error': str(e), 'crawl_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } finally: if driver: driver.quit() def crawl_batch(self, sku_list, proxy_list=None, callback=None): """ 批量爬取 :param sku_list: 商品ID列表 :param proxy_list: 代理列表,轮换使用 :param callback: 进度回调函数 """ total = len(sku_list) completed = 0 with ThreadPoolExecutor(max_workers=self.max_workers) as executor: futures = {} for idx, sku_id in enumerate(sku_list): # 轮换代理 proxy = None if proxy_list: proxy = proxy_list[idx % len(proxy_list)] future = executor.submit(self.crawl_single_sku, sku_id, proxy) futures[future] = sku_id for future in as_completed(futures): result = future.result() self.results.append(result) if not result.get('success'): self.failed_skus.append(result['sku_id']) completed += 1 if callback: callback(completed, total) # 在大批量爬取时,每50个休息10秒 if completed % 50 == 0: print(f'已完成 {completed}/{total},休息10秒...') time.sleep(10) return self.results def save_to_excel(self, filename='jd_prices.xlsx'): """保存结果到Excel""" df = pd.DataFrame(self.results) df.to_excel(filename, index=False) print(f'结果已保存到 {filename}') return df def save_to_json(self, filename='jd_prices.json'): """保存结果到JSON""" with open(filename, 'w', encoding='utf-8') as f: json.dump(self.results, f, ensure_ascii=False, indent=2) print(f'结果已保存到 {filename}') def generate_report(self): """生成爬取报告""" total = len(self.results) success = sum(1 for r in self.results if r.get('success')) failed = total - success report = f""" ========== 京东价格爬取报告 ========== 爬取时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 总商品数: {total} 成功数量: {success} ({success/total*100:.1f}%) 失败数量: {failed} ({failed/total*100:.1f}%) 并发线程: {self.max_workers} 请求间隔: {self.request_interval}秒 价格统计(成功的数据): """ if success > 0: prices = [r['price'] for r in self.results if r.get('price')] if prices: report += f""" 最高价格: ¥{max(prices):.2f} 最低价格: ¥{min(prices):.2f} 平均价格: ¥{sum(prices)/len(prices):.2f} """ report += f""" 失败商品ID列表(前20个): {self.failed_skus[:20]} """ print(report) return report

3.4 防封号策略增强版

上面的代码已经实现基础功能,但要想长时间稳定运行,还需要一个专门的防封模块。

python

# anti_ban.py import random import time import pickle import os from selenium.webdriver.common.by import By class AntiBanManager: """反反爬虫管理器""" def __init__(self, driver): self.driver = driver self.cookie_file = 'jd_cookies.pkl' def human_like_behavior(self): """ 模拟人类行为: - 随机滚动页面 - 随机鼠标移动 - 随机等待时间 """ # 随机滚动到页面不同位置 scroll_positions = [100, 300, 500, 800, 1200, 'bottom'] scroll_to = random.choice(scroll_positions) if scroll_to == 'bottom': self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") else: self.driver.execute_script(f"window.scrollTo(0, {scroll_to});") # 随机停留 time.sleep(random.uniform(0.5, 2)) # 10%的概率会鼠标移动(模拟) if random.random() < 0.1: # 这里可以用ActionChains实现更真实的鼠标移动 pass # 5%的概率会回滚到顶部 if random.random() < 0.05: self.driver.execute_script("window.scrollTo(0, 0);") time.sleep(random.uniform(0.3, 1)) def load_cookies(self): """加载之前保存的cookies""" if os.path.exists(self.cookie_file): try: with open(self.cookie_file, 'rb') as f: cookies = pickle.load(f) for cookie in cookies: # 处理过期cookie if 'expiry' in cookie: cookie['expiry'] = int(cookie['expiry']) self.driver.add_cookie(cookie) print(f'成功加载 {len(cookies)} 个cookies') return True except Exception as e: print(f'加载cookies失败: {e}') return False def save_cookies(self): """保存cookies供下次使用""" try: cookies = self.driver.get_cookies() with open(self.cookie_file, 'wb') as f: pickle.dump(cookies, f) print(f'成功保存 {len(cookies)} 个cookies') return True except Exception as e: print(f'保存cookies失败: {e}') return False def login_if_needed(self, username=None, password=None): """ 如果需要登录,执行登录操作 注意:京东的登录有滑块验证,手动登录更靠谱 """ # 检查是否需要登录 if 'passport' in self.driver.current_url or 'login' in self.driver.current_url: print('检测到需要登录,请手动扫码登录...') # 等待用户手动登录(给你60秒时间扫码) for i in range(60): time.sleep(1) if 'passport' not in self.driver.current_url and 'login' not in self.driver.current_url: print('登录成功!') self.save_cookies() return True print('登录超时') return False return True def random_delay(self, min_sec=1, max_sec=3): """随机延迟,避免规律性请求""" delay = random.uniform(min_sec, max_sec) time.sleep(delay) def check_ip_blocked(self): """检查IP是否被封""" page_source = self.driver.page_source.lower() blocked_keywords = ['拒绝访问', 'access denied', '验证', 'captcha', '滑块'] for keyword in blocked_keywords: if keyword in page_source: print(f'检测到可能被封: {keyword}') return True return False

3.5 主程序与使用示例

python

# main.py import random import sys from colorama import init, Fore, Style # 可选,用于彩色输出 # 初始化colorama init(autoreset=True) def print_banner(): """打印程序横幅""" banner = f""" {Fore.CYAN}╔══════════════════════════════════════════════════════════╗ ║ 京东商品价格爬虫 v2.0 - 动态加载破解版 ║ ║ 支持批量爬取 | 自动换IP | 反反爬虫策略 ║ ╚══════════════════════════════════════════════════════════╝{Style.RESET_ALL} """ print(banner) def show_progress(current, total): """显示进度条""" percent = current / total * 100 bar_length = 40 filled = int(bar_length * current // total) bar = '█' * filled + '░' * (bar_length - filled) sys.stdout.write(f'\r进度: |{bar}| {percent:.1f}% ({current}/{total})') sys.stdout.flush() def main(): print_banner() # ========== 配置区域 ========== # 要爬取的商品ID列表(可以从Excel或数据库读取) sku_list = [ '100012043978', # 示例:iPhone 15 '100038009238', # 示例:某款笔记本 '100012345678', # 替换成你需要的商品ID # 可以添加更多... ] # 代理列表(可选,建议购买付费代理) proxy_list = [ # 'http://user:pass@ip:port', # 'http://user:pass@ip:port', ] # 爬虫参数 MAX_WORKERS = 2 # 并发数,京东很敏感,建议从1或2开始 REQUEST_INTERVAL = 5 # 请求间隔秒数,越大越安全 # ========== 开始爬取 ========== print(f'{Fore.YELLOW}[INFO] 开始爬取,共 {len(sku_list)} 个商品') print(f'[INFO] 并发数: {MAX_WORKERS}, 请求间隔: {REQUEST_INTERVAL}秒{Style.RESET_ALL}') # 创建爬虫实例 crawler = JDBatchCrawler( driver_config=ChromeDriverConfig, max_workers=MAX_WORKERS, request_interval=REQUEST_INTERVAL ) # 执行批量爬取 results = crawler.crawl_batch( sku_list=sku_list, proxy_list=proxy_list if proxy_list else None, callback=show_progress ) print('\n') # 换行 # ========== 保存结果 ========== if results: # 保存为Excel df = crawler.save_to_excel('jd_prices_{}.xlsx'.format( datetime.now().strftime('%Y%m%d_%H%M%S') )) # 保存为JSON crawler.save_to_json('jd_prices_{}.json'.format( datetime.now().strftime('%Y%m%d_%H%M%S') )) # 生成报告 crawler.generate_report() # 打印成功的结果 print(f'\n{Fore.GREEN}[SUCCESS] 爬取完成!成功爬取到价格的商品:{Style.RESET_ALL}') success_results = [r for r in results if r.get('success')] for r in success_results[:10]: # 只显示前10个 print(f' 商品 {r["sku_id"]}: ¥{r["price"]} (原价: {r.get("original_price", "无")})') if len(success_results) > 10: print(f' ... 共{len(success_results)}个成功') else: print(f'{Fore.RED}[ERROR] 没有获取到任何数据{Style.RESET_ALL}') def test_single_product(): """测试单个商品,用于调试""" sku_id = input('请输入商品ID: ').strip() print(f'正在爬取商品 {sku_id} ...') driver = ChromeDriverConfig.get_driver() extractor = JDPriceExtractor(driver) result = extractor.extract_price_from_detail_page(sku_id) if result['success']: print(f'✅ 成功!价格: ¥{result["price"]}') if result.get('original_price'): print(f'原价: ¥{result["original_price"]}') if result.get('promotion_info'): print(f'促销信息: {result["promotion_info"]}') else: print(f'❌ 失败: {result.get("error", "未知错误")}') driver.quit() if __name__ == '__main__': import argparse from datetime import datetime parser = argparse.ArgumentParser(description='京东商品价格爬虫') parser.add_argument('--mode', choices=['batch', 'test'], default='batch', help='运行模式: batch批量爬取, test测试单个商品') args = parser.parse_args() if args.mode == 'test': test_single_product() else: main()

四、踩坑实录与解决方案

写这个爬虫的过程中,我遇到了无数坑,这里把最典型的几个列出来。

4.1 坑一:无头模式被检测

最开始我用headless=True,心想这样节省资源。结果京东直接返回空白页面或者验证码。

原因:无头模式下navigator.webdriver属性为true,京东的JS能检测到。

解决方案

  • 放弃无头模式,用有头模式

  • 如果一定要无头,需要添加--headless=new并配合上面代码中的CDP脚本

4.2 坑二:chromedriver版本不匹配

有一次Chrome自动更新了,我的爬虫突然报错session not created。折腾了半天发现是驱动版本不对。

解决方案:用webdriver-manager自动管理,再也不用担心版本问题。

4.3 坑三:价格明明在页面上但就是抓不到

观察发现,商品价格不是一开始就加载的,需要滚动到价格区域才会触发加载。

解决方案:在等待价格元素之前,先执行一个滚动动作到价格区域附近。

python

# 滚动到价格可能出现的位置 self.driver.execute_script("window.scrollTo(0, 300);") time.sleep(0.5)

4.4 坑四:京东的随机class名

京东给价格元素加的是动态class,比如p-price-9f3k2d1,每次刷新都不一样。

解决方案:用属性选择器或者部分匹配,比如[class*="p-price"],或者用稳定的ID如#jd-price

4.5 坑五:触发验证码后被无限重定向

一旦触发验证码,京东会让你一直验证,即使你关了浏览器重新开,IP已经被标记了。

解决方案

  • 使用高质量的代理IP

  • 降低请求频率

  • 实现验证码识别(这个成本较高,建议直接换IP)

4.6 坑六:Cookie过期后无法获取价格

Cookie默认有效期大概24小时,过期后访问商品页会跳转到登录页。

解决方案

  • 实现Cookie持久化存储和加载

  • 定时登录更新Cookie(但京东登录有滑块,建议手动定期更新)

五、性能优化与分布式扩展

5.1 单机优化

  • 复用driver:不要每个商品都启动关闭浏览器,一个driver可以爬多个商品

  • 减少等待时间:将显式等待的超时时间从10秒降到5秒

  • 使用CSS选择器而不是XPath:CSS选择器效率更高

5.2 分布式爬虫架构

当商品数量达到万级以上,单机不够用了。可以考虑:

  1. Redis作为URL队列:Master节点分发任务,Worker节点消费

  2. 代理池:用Redis维护一个代理IP池,自动检测可用性

  3. 数据库存储:MySQL/PostgreSQL存储结果,避免重复爬取

下面是一个简化的分布式任务队列实现:

python

# distributed_queue.py import redis import json class RedisTaskQueue: """基于Redis的分布式任务队列""" def __init__(self, host='localhost', port=6379, db=0): self.redis_client = redis.Redis(host=host, port=port, db=db) self.task_key = 'jd:price:tasks' self.result_key = 'jd:price:results' def push_tasks(self, sku_list): """批量推送任务""" for sku_id in sku_list: self.redis_client.lpush(self.task_key, sku_id) print(f'已推送 {len(sku_list)} 个任务') def pop_task(self, timeout=5): """获取任务(阻塞模式)""" result = self.redis_client.brpop(self.task_key, timeout=timeout) if result: return result[1].decode('utf-8') return None def save_result(self, sku_id, price_data): """保存爬取结果""" data = { 'sku_id': sku_id, 'price_data': price_data, 'timestamp': datetime.now().isoformat() } self.redis_client.lpush(self.result_key, json.dumps(data)) def get_results(self): """获取所有结果""" results = [] while True: result = self.redis_client.rpop(self.result_key) if not result: break results.append(json.loads(result)) return results

六、法律与道德提醒

写爬虫之前,有几点必须要说明:

  1. 遵守robots.txt:京东的https://www.jd.com/robots.txt明确禁止了部分爬取行为。严格来说,批量爬取商品数据可能违反服务条款。

  2. 控制请求频率:不要对京东服务器造成压力,这是基本礼貌。

  3. 数据使用:爬下来的价格数据不要用于商业目的,尤其是不要做比价平台与京东直接竞争。

  4. IP代理:使用代理时,确保代理来源合法。

  5. 尊重用户隐私:不要爬取用户评论中的个人信息。

http://www.jsqmd.com/news/815165/

相关文章:

  • 如何免费永久使用Cursor Pro:3步解决试用限制的终极方案
  • 2026年充电桩与发电机组口碑榜:直流充电桩、静音发电机组、重卡充电桩厂家优选指南 - 海棠依旧大
  • 6 秒创建 Postgres 数据库副本!Ardent 助力编码代理高效验证代码,优势远超传统方式
  • Taotoken API Key安全管理最佳实践与审计日志查看
  • 风险只有在未发生时才叫风险,发生之后,它叫损失——致我的25岁
  • Residue开源项目:为AI编程对话建立可追溯的代码记忆库
  • 基于Alpaca API的量化交易系统构建:从策略开发到实盘部署
  • 光储微网孤岛检测与VSG切换控制【附程序】
  • 对比官方价,Taotoken活动价带来的Token成本优势感知
  • 魔百和CM311-1A刷机后体验:ADB默认开启、纯净安卓9系统到底有多流畅?
  • 3个惊艳用法:让APK安装器彻底改变你的Windows安卓体验
  • CircuitPython嵌入式开发入门:从LED闪烁到传感器读取实战
  • ODRP开发日记-靠近NPC触发交互(二)
  • Android万能播放器终极指南:OPlayer开源项目完整解析与快速上手
  • 终极指南:一劳永逸解决Windows软件运行问题的Visual C++运行库全家桶
  • 深度解析智能歌词同步工具:macOS用户的革命性解决方案
  • 终极指南:5分钟快速免费解锁Cursor AI编程助手Pro功能完整教程
  • Credenza:现代化密钥管理工具的设计、部署与集成实践
  • 立创EDA铺铜后别急着收工!这个‘批量过孔’功能,能让你的PCB稳定性翻倍
  • Android虚拟摄像头终极指南:2025年完全控制摄像头输入的新方案
  • WebPeel:为AI Agent设计的Web数据层,实现高效网页内容提取
  • ESP32开源无人机实战指南:从零打造你的智能飞行器
  • 用Python脚本检测MP4/QuickTime视频里的‘幽灵数据’:一个数字取证小实验
  • Gemini-CLI视觉扩展:让命令行终端具备AI视觉与多模态交互能力
  • 量子噪声控制与FIR滤波器应用解析
  • 如何用TQVaultAE解决泰坦之旅无限仓库存储难题?
  • 终极指南:3分钟为Axure RP安装免费中文语言包
  • 书匠策AI(http://www.shujiangce.com)期刊论文功能全拆解
  • 别再手动算结果了!Fluent自定义场函数实战:从创建、可视化到单位制避坑(附SCM文件管理)
  • 人体冷冻技术:从玻璃化原理到未来复活的科学伦理探索