Python 网络爬虫实战:使用 Scrapy + MongoDB 爬取京东网站并部署到云服务器上
'# Python 网络爬虫实战:使用 Scrapy + MongoDB 爬取京东网站并部署到云服务器上
一、背景与问题
在互联网数据采集场景中,网络爬虫是获取结构化数据的核心工具。京东作为中国最大的电商平台之一,其商品信息、价格、评论等数据具有极高的商业价值。然而,京东网站采用了复杂的反爬机制,包括动态渲染、验证码识别、IP封锁等,这对传统爬虫方案提出了严峻挑战。
传统爬虫方案在处理动态网页时容易遇到以下问题:
- JavaScript渲染内容无法直接解析
- 验证码识别需要额外处理
- 请求频率限制导致IP被封
- 数据存储效率低下
本方案采用Scrapy框架结合MongoDB数据库,通过分布式爬虫架构和数据分片策略,构建一个可扩展的爬虫系统,同时探讨其适用场景与技术边界。
二、基本原理
1. Scrapy框架架构
Scrapy采用典型的爬虫架构,包含以下核心组件:
Spider
│
├─ Engine
│ ├─ Scheduler(调度器)
│ ├─ Downloader(下载器)
│ └─ Parser(解析器)
│
└─ Pipeline(数据处理管道)关键流程:
- Spider发起初始请求
- Engine将请求发送给Scheduler
- Scheduler选择请求分发给Downloader
- Downloader获取响应后传递给Parser
- Parser提取数据并生成Item
- Item通过Pipeline进行数据处理和存储
2. MongoDB存储机制
MongoDB采用文档存储模型,其核心特征包括:
- 非结构化数据存储
- 支持JSON格式的查询
- 内置的分片和复制机制
- 水平扩展能力
在爬虫场景中,MongoDB的自动分片能力可以有效解决数据增长带来的性能瓶颈。
三、环境准备
1. 开发环境配置
# 安装Scrapy框架
pip install scrapy
# 安装MongoDB驱动
pip install pymongo
# 安装代理服务(可选)
pip install requests2. 云服务器配置
推荐使用阿里云ECS实例(Ubuntu 20.04 LTS),配置如下:
- CPU:4核
- 内存:8GB
- 存储:50GB SSD
- 网络:公网IP
四、核心实现
1. Scrapy Spider实现
# items.py
import scrapy
class JDItem(scrapy.Item):
product_id = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
sales = scrapy.Field()
comment_count = scrapy.Field()
category = scrapy.Field()
url = scrapy.Field()
image_urls = scrapy.Field()# jd_spider.py
import scrapy
from ..items import JDItem
class JDScraperSpider(scrapy.Spider):
name = 'jd_scraper'
allowed_domains = ['www.jd.com']
start_urls = ['https://www.jd.com/allprod.html']
def parse(self, response):
# 提取商品列表
for item in response.css('li.product-item'):
yield JDItem(
product_id=item.xpath('@data-id').get(),
title=item.css('div.title::text').get(),
price=item.css('strong.price::text').get(),
sales=item.css('span.sales::text').get(),
comment_count=item.css('span.comment::text').get(),
category=item.css('span.category::text').get(),
url=item.css('a::attr(href)').get(),
image_urls=item.css('img::attr(src)').getall()
)
# 处理分页
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)2. 数据处理Pipeline
# pipelines.py
import pymongo
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem
class JDDataPipeline:
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DB')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
self.collection = self.db['products']
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
# 数据清洗
for key in item:
if item[key] is None:
item[key] = ''
# 插入数据库
self.collection.update_one(
{'product_id': item['product_id']},
{'$set': dict(item)},
upsert=True
)
return item3. 配置文件
# settings.py
BOT_NAME = 'jd_scraper'
SPIDER_MODULES = ['jd_scraper.spiders']
NEWSPIDER_MODULE = 'jd_scraper.spiders'
# MongoDB配置
MONGO_URI = 'mongodb://admin:password@localhost:27017'
MONGO_DB = 'jd_data'
# 爬虫配置
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4443.116 Safari/537.36'
DOWNLOAD_DELAY = 2
CONCURRENT_REQUESTS = 16
CONCURRENT_ITEMS = 100五、完整案例
1. 项目结构
jd_crawler/
├── jd_scraper/
│ ├── __init__.py
│ ├── items.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders/
│ └── jd_spider.py
├── Dockerfile
├── docker-compose.yml
└── run.sh2. 完整爬虫流程
# run.sh
#!/bin/bash
# 启动MongoDB容器
docker run -d --name mongodb -p 27017:27017 mongo
# 启动爬虫容器
docker run -d --name jd_crawler \
--network host \
-v $(pwd)/jd_scraper:/app/jd_scraper \
-v $(pwd)/data:/data \
-e MONGO_URI="mongodb://admin:password@localhost:27017" \
-e MONGO_DB="jd_data" \
-e SPIDER_NAME="jd_scraper" \
-e LOG_LEVEL="INFO" \
my-jd-crawler3. 云服务器部署
# 安装Docker
sudo apt-get update
sudo apt-get install docker.io
# 安装Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 创建Dockerfile
FROM python:3.8-slim
WORKDIR /app
COPY . /app
RUN pip install scrapy pymongo
CMD ["sh", "run.sh"]六、源码解析
1. Scrapy Spider核心逻辑
def parse(self, response):
# 处理动态内容
for item in response.css('li.product-item'):
yield JDItem(
product_id=item.xpath('@data-id').get(),
title=item.css('div.title::text').get(),
price=item.css('strong.price::text').get(),
sales=item.css('span.sales::text').get(),
comment_count=item.css('span.comment::text').get(),
category=item.css('span.category::text').get(),
url=item.css('a::attr(href)').get(),
image_urls=item.css('img::attr(src)').getall()
)关键点:
- 使用XPath和CSS选择器处理网页结构
- 提取动态属性
data-id作为唯一标识 - 收集多张商品图片URL
2. MongoDB Pipeline优化
def process_item(self, item, spider):
# 数据清洗
for key in item:
if item[key] is None:
item[key] = ''
# 插入数据库
self.collection.update_one(
{'product_id': item['product_id']},
{'$set': dict(item)},
upsert=True
)
return item优化点:
- 使用
upsert避免重复插入 - 转换为字典格式确保兼容性
- 增加空值处理防止存储异常
七、进阶使用
1. 分布式爬虫架构
# 分布式爬虫配置
SPIDER_MIDDLEWARES = {
'scrapy.extensions.telnet.TelnetMiddleware': 200,
'jd_scraper.middlewares.JDProxyMiddleware': 100,
}
DOWNLOAD_HANDLERS = {
'http': 'scrapy.http.client.AsyncHTTPClient',
'https': 'scrapy.http.client.AsyncHTTPClient',
}2. 验证码处理方案
# 验证码识别模块
import requests
import base64
def solve_captcha(image_data):
# 调用第三方验证码识别API
response = requests.post(
'https://api.captcha.com/recognize',
data=base64.b64encode(image_data).decode('utf-8')
)
return response.json()['text']3. 代理池集成
# 代理中间件
class JDProxyMiddleware:
def process_request(self, request, spider):
# 获取代理
proxy = self.get_random_proxy()
request.meta['proxy'] = proxy八、性能与工程实践
1. 性能优化策略
| 优化措施 | 说明 |
|---|---|
| 并发控制 | 使用CONCURRENT_REQUESTS限制并发数 |
| 延迟设置 | DOWNLOAD_DELAY控制请求间隔 |
| 内存管理 | 避免大量数据缓存 |
| 网络优化 | 使用COOKIES_ENABLED=False |
2. 异常处理机制
def parse(self, response):
try:
# 主要逻辑
except Exception as e:
logger.error(f"Error processing {response.url}: {str(e)}")
return3. 安全防护措施
| 风险点 | 解决方案 |
|---|---|
| IP封锁 | 使用代理池 |
| 验证码识别 | 调用第三方服务 |
| 数据泄露 | 加密传输 |
| 非法访问 | 访问频率限制 |
九、常见问题与踩坑
1. 常见错误分析
| 错误类型 | 原因 | 解决方案 |
|---|---|---|
| 429错误 | 请求频率过高 | 增加DOWNLOAD_DELAY |
| 503错误 | 服务暂时不可用 | 增加重试机制 |
| KeyError | 字段不存在 | 增加默认值处理 |
| ConnectionRefused | MongoDB连接失败 | 检查网络配置 |
2. 京东反爬机制应对
# 设置请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4443.116 Safari/537.36',
'Referer': 'https://www.jd.com/',
'Accept-Language': 'zh-CN,zh;q=0.9'
}3. 数据存储问题
# 增加索引优化
self.collection.create_index([('product_id', pymongo.ASCENDING)], unique=True)十、最佳实践
1. 推荐配置方案
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| 并发数 | 16 | 平衡性能与稳定性 |
| 延迟时间 | 2s | 避免触发反爬机制 |
| 日志级别 | INFO | 关键信息记录 |
| 代理池 | 50+ | 确保IP可用性 |
2. 实施建议
- 使用Docker容器化部署
- 建立独立的爬虫服务器集群
- 配置自动清理机制
- 部署监控告警系统
- 定期更新爬虫规则
十一、总结
本文深入探讨了使用Scrapy和MongoDB爬取京东网站的完整方案,涵盖从基础实现到高级优化的各个方面。通过分析京东的反爬机制,我们展示了如何构建一个可扩展的爬虫系统,并探讨了其适用场景与技术边界。
在实际应用中,该方案特别适合:
- 需要处理复杂网页结构的场景
- 需要长期稳定采集的场景
- 需要存储非结构化数据的场景
但需要注意:
- 不适合频繁更新的动态数据
- 不适合需要高并发的实时数据
- 不适合涉及敏感信息的采集场景
通过合理配置和持续优化,该方案可以作为企业级数据采集的可靠解决方案。在实际部署中,建议结合监控系统和自动化运维工具,构建完整的数据采集生态系统。
评论已关闭