Scrapy 实践应用 Scrapy 实践应用:从入门到精通 我们将重点关注以下几个方面: 4.1 爬取静态网站:商品信息抓取 4.2 爬取动态网站:模拟登录与数据提取 4.3 数据存储与处理:Pipeline 的应用 4.4 中间件的应用:反爬策略应对 4.5 分布式爬虫:Scrapy-Redis 集成 4.1 爬取静态网站:商品信息抓取 让我们从一个简单的例子开始:爬取一个静态电商网站的商品信息,例如商品名称、价格和图片链接。 4.1.1 创建 Scrapy 项目 首先,创建一个新的 Scrapy 项目: 4.1.2 定义 Item 在 文件中,定义要抓取的数据结构: 4.1.3 创建 Spider 在 目录下,创建一个新的 Spider 文件,例如 : 4.1.
我们将重点关注以下几个方面:
4.1 爬取静态网站:商品信息抓取
4.2 爬取动态网站:模拟登录与数据提取
4.3 数据存储与处理:Pipeline 的应用
4.4 中间件的应用:反爬策略应对
4.5 分布式爬虫:Scrapy-Redis 集成
让我们从一个简单的例子开始:爬取一个静态电商网站的商品信息,例如商品名称、价格和图片链接。
4.1.1 创建 Scrapy 项目
首先,创建一个新的 Scrapy 项目:
scrapy startproject ecom_spider cd ecom_spider
4.1.2 定义 Item
在 items.py 文件中,定义要抓取的数据结构:
# items.py import scrapy class ProductItem(scrapy.Item): name = scrapy.Field() price = scrapy.Field() image_url = scrapy.Field() url = scrapy.Field() # 商品链接 pass
4.1.3 创建 Spider
在 spiders 目录下,创建一个新的 Spider 文件,例如 product_spider.py:
# spiders/product_spider.py import scrapy from ecom_spider.items import ProductItem class ProductSpider(scrapy.Spider): name = "product_spider" allowed_domains = ["example.com"] # 替换为你的目标网站域名 start_urls = ["http://www.example.com/products"] # 替换为你的起始 URL def parse(self, response): # 使用 CSS 选择器或 XPath 提取商品信息 products = response.css('div.product') # 假设商品信息在 div.product 中 for product in products: item = ProductItem() item['name'] = product.css('h2.product-name::text').get() item['price'] = product.css('span.price::text').get() item['image_url'] = product.css('img.product-image::attr(src)').get() item['url'] = response.urljoin(product.css('a::attr(href)').get()) # 获取完整URL yield item # 提取下一页链接并继续爬取 next_page = response.css('a.next-page::attr(href)').get() if next_page: yield response.follow(next_page, self.parse)
4.1.4 运行 Spider
运行 Spider 并将结果保存到 JSON 文件:
scrapy crawl product_spider -o products.json
代码解释:
name: Spider 的名称,用于标识 Spider。
allowed_domains: 允许爬取的域名,防止爬虫爬到其他网站。
start_urls: 爬虫的起始 URL。
parse(self, response): 解析响应的函数。它接收一个 response 对象,其中包含服务器返回的 HTML 内容。
response.css(): 使用 CSS 选择器提取数据。
response.xpath(): 使用 XPath 提取数据(也可以使用)。
item = ProductItem(): 创建一个 ProductItem 实例,用于存储提取的数据。
yield item: 将提取的数据传递给 Pipeline 进行处理。
response.follow(): 提取链接并生成新的请求,用于爬取下一页。 response.urljoin() 用于将相对 URL 转换为绝对 URL。
4.1.5 Mermaid 图:爬取流程
对于需要登录才能访问的网站,我们需要模拟登录过程。
4.2.1 模拟登录
首先,找到登录表单的 URL 和提交参数。可以使用浏览器的开发者工具来检查网络请求。
# spiders/login_spider.py import scrapy class LoginSpider(scrapy.Spider): name = "login_spider" allowed_domains = ["example.com"] login_url = "http://www.example.com/login" # 替换为登录 URL start_urls = ["http://www.example.com/profile"] # 替换为登录后才能访问的页面 def start_requests(self): yield scrapy.Request( self.login_url, callback=self.login, ) def login(self, response): # 提取 CSRF token (如果存在) csrf_token = response.css('input[name="csrf_token"]::attr(value)').get() yield scrapy.FormRequest( self.login_url, formdata={ "username": "your_username", # 替换为你的用户名 "password": "your_password", # 替换为你的密码 "csrf_token": csrf_token, # 添加 CSRF token (如果需要) }, callback=self.after_login, ) def after_login(self, response): # 检查登录是否成功 if "Welcome" in response.text: # 替换为登录成功的标志 self.log("登录成功!") # 继续爬取需要登录才能访问的页面 yield scrapy.Request(self.start_urls[0], callback=self.parse_profile) else: self.log("登录失败!") def parse_profile(self, response): # 解析用户个人资料页面 # ... pass
代码解释:
start_requests(): 覆盖此方法以发送登录请求。
scrapy.FormRequest: 用于发送 POST 请求,模拟表单提交。
formdata: 包含登录表单的参数。
callback: 指定处理登录响应的函数。
after_login(): 检查登录是否成功,并继续爬取需要登录才能访问的页面。
CSRF token: 许多网站使用 CSRF token 来防止跨站请求伪造。需要在登录请求中包含正确的 CSRF token。
4.2.2 使用 Cookies 进行会话保持
Scrapy 会自动处理 Cookies,因此在登录后,它可以自动保持会话。
4.2.3 处理 JavaScript 渲染的页面
对于使用 JavaScript 渲染内容的网站,Scrapy 本身无法直接抓取。可以使用 Scrapy 与 Selenium 或 Splash 集成,Selenium 模拟浏览器行为,Splash 是一个 JavaScript 渲染服务。
Pipeline 用于处理 Spider 提取的数据。它可以用于数据清洗、验证、存储到数据库等。
4.3.1 定义 Pipeline
在 pipelines.py 文件中,定义 Pipeline 类:
# pipelines.py import json class EcomSpiderPipeline: def __init__(self): self.file = open("products.json", "w", encoding="utf-8") self.items = [] def process_item(self, item, spider): # 数据清洗和验证 if not item['price']: item['price'] = "价格未知" self.items.append(item) return item def close_spider(self, spider): json.dump(self.items, self.file, ensure_ascii=False, indent=4) self.file.close() class PriceValidationPipeline: def process_item(self, item, spider): try: item['price'] = float(item['price'].replace('$', '').replace(',', '')) # 清理价格数据 except: item['price'] = None # 设置为 None 表示无效价格 return item
代码解释:
process_item(self, item, spider): 处理每个 Item 的函数。
close_spider(self, spider): 在 Spider 关闭时调用的函数。
__init__(self): 初始化Pipeline,这里打开文件准备写入。
4.3.2 启用 Pipeline
在 settings.py 文件中,启用 Pipeline:
# settings.py ITEM_PIPELINES = { 'ecom_spider.pipelines.PriceValidationPipeline': 100, 'ecom_spider.pipelines.EcomSpiderPipeline': 300, }
注意: Pipeline 的顺序由数字决定,数字越小,优先级越高。
中间件用于处理 Scrapy 的请求和响应。它可以用于添加 User-Agent、设置代理、处理重试等。
4.4.1 User-Agent 中间件
添加 User-Agent 可以伪装爬虫,避免被网站识别。
# middlewares.py import random class RandomUserAgentMiddleware: def __init__(self, user_agent_list): self.user_agent_list = user_agent_list @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('USER_AGENT_LIST')) def process_request(self, request, spider): user_agent = random.choice(self.user_agent_list) request.headers['User-Agent'] = user_agent
在 settings.py 中配置:
# settings.py DOWNLOADER_MIDDLEWARES = { 'ecom_spider.middlewares.RandomUserAgentMiddleware': 400, } USER_AGENT_LIST = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15', ]
4.4.2 代理中间件
使用代理可以隐藏爬虫的真实 IP 地址。
# middlewares.py import random class ProxyMiddleware: def __init__(self, proxy_list): self.proxy_list = proxy_list @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('PROXY_LIST')) def process_request(self, request, spider): proxy = random.choice(self.proxy_list) request.meta['proxy'] = proxy
在 settings.py 中配置:
# settings.py DOWNLOADER_MIDDLEWARES = { 'ecom_spider.middlewares.ProxyMiddleware': 750, } PROXY_LIST = [ 'http://10.10.1.10:3128', 'http://10.10.1.11:1080', ]
4.4.3 Retry 中间件
Scrapy 自带 Retry 中间件,可以自动重试失败的请求。可以在 settings.py 中配置重试次数和状态码。
# settings.py RETRY_ENABLED = True RETRY_TIMES = 3 RETRY_HTTP_CODES = [500, 502, 503, 504, 400, 408]
对于大型爬虫项目,可以使用 Scrapy-Redis 将 Scrapy 改造为分布式爬虫。
4.5.1 安装 Scrapy-Redis
pip install scrapy-redis
4.5.2 配置 Scrapy-Redis
在 settings.py 文件中,配置 Scrapy-Redis:
# settings.py SCHEDULER = "scrapy_redis.scheduler.Scheduler" DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" REDIS_URL = 'redis://localhost:6379' # Redis 连接地址 ITEM_PIPELINES = { 'ecom_spider.pipelines.EcomSpiderPipeline': 300, 'scrapy_redis.pipelines.RedisPipeline': 400, # 将 Item 存储到 Redis }
4.5.3 修改 Spider
修改 Spider,使其从 Redis 读取起始 URL:
# spiders/redis_spider.py import scrapy from scrapy_redis.spiders import RedisSpider class MySpider(RedisSpider): name = 'redis_spider' redis_key = 'start_urls' # 从 Redis 读取 start_urls 的 key def parse(self, response): # ... pass
4.5.4 将起始 URL 推送到 Redis
使用 Redis 客户端将起始 URL 推送到 Redis:
import redis r = redis.Redis(host='localhost', port=6379) r.lpush('start_urls', 'http://www.example.com/products')
4.5.5 运行 Spider
运行 Spider:
scrapy crawl redis_spider
4.5.6 分布式部署
将 Spider 部署到多台机器上,它们将共享 Redis 中的 URL 队列,实现分布式爬取。
总结:
通过以上实例,我们学习了 Scrapy 的基本应用,包括静态网站和动态网站的爬取、数据存储与处理、反爬策略应对以及分布式爬虫的实现。Scrapy 是一个非常灵活和强大的爬虫框架,可以用于各种复杂的爬虫任务。希望本文能够帮助你更好地理解和应用 Scrapy。记住,实践是最好的老师,多动手尝试,才能真正掌握 Scrapy。