Scrapy框架的使用之Scrapy对接Selenium

Scrapy抓取页面的方式和requests库类似,都是直接模拟HTTP请求,而Scrapy也不能抓取JavaScript动态渲染的页面。抓取JavaScript渲染的页面有两种方式。一种是分析Ajax请求,找到其对应的接口抓取,Scrapy同样可以用此种方式抓取。另一种是直接用Selenium或Splash模拟浏览器进行抓取,我们不需要关心页面后台发生的请求,也不需要分析渲染过程,只需要关心页面最终结果即可,可见即可爬。那么,如果Scrapy可以对接Selenium,那Scrapy就可以处理任何网站的抓取了。

一、本节目标

本节我们来看看Scrapy框架如何对接Selenium,以PhantomJS进行演示。我们依然抓取淘宝商品信息,抓取逻辑和前文中用Selenium抓取淘宝商品完全相同。

二、准备工作

请确保PhantomJS和MongoDB已经安装好并可以正常运行,安装好Scrapy、Selenium、PyMongo库。

三、新建项目

首先新建项目,名为scrapyseleniumtest,命令如下所示:

scrapy startproject scrapyseleniumtest

新建一个Spider,命令如下所示:

scrapy genspider taobao www.taobao.com

修改ROBOTSTXT_OBEYFalse,如下所示:

ROBOTSTXT_OBEY = False

四、定义 Item

首先定义Item对象,名为ProductItem,代码如下所示:

from scrapy import Item, Field

class ProductItem(Item):

   collection = 'products'
   image = Field()
   price = Field()
   deal = Field()
   title = Field()
   shop = Field()
   location = Field()

这里我们定义了6个Field,也就是6个字段,跟之前的案例完全相同。然后定义了一个collection属性,即此Item保存的MongoDB的Collection名称。

初步实现Spider的start_requests()方法,如下所示:

from scrapy import Request, Spider
from urllib.parse import quote
from scrapyseleniumtest.items import ProductItem

class TaobaoSpider(Spider):
   name = 'taobao'
   allowed_domains = ['www.taobao.com']
   base_url = 'https://s.taobao.com/search?q='

   def start_requests(self):
       for keyword in self.settings.get('KEYWORDS'):
           for page in range(1, self.settings.get('MAX_PAGE') + 1):
               url = self.base_url + quote(keyword)
               yield Request(url=url, callback=self.parse, meta={'page': page}, dont_filter=True)

首先定义了一个base_url,即商品列表的URL,其后拼接一个搜索关键字就是该关键字在淘宝的搜索结果商品列表页面。

关键字用KEYWORDS标识,定义为一个列表。最大翻页页码用MAX_PAGE表示。它们统一定义在setttings.py里面,如下所示:

KEYWORDS = ['iPad']
MAX_PAGE = 100

start_requests()方法里,我们首先遍历了关键字,遍历了分页页码,构造并生成Request。由于每次搜索的URL是相同的,所以分页页码用meta参数来传递,同时设置dont_filter不去重。这样爬虫启动的时候,就会生成每个关键字对应的商品列表的每一页的请求了。

五、对接 Selenium

接下来我们需要处理这些请求的抓取。这次我们对接Selenium进行抓取,采用Downloader Middleware来实现。在Middleware里面的process_request()方法里对每个抓取请求进行处理,启动浏览器并进行页面渲染,再将渲染后的结果构造一个HtmlResponse对象返回。代码实现如下所示:

from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from scrapy.http import HtmlResponse
from logging import getLogger

class SeleniumMiddleware():
   def __init__(self, timeout=None, service_args=[]):
       self.logger = getLogger(__name__)
       self.timeout = timeout
       self.browser = webdriver.PhantomJS(service_args=service_args)
       self.browser.set_window_size(1400, 700)
       self.browser.set_page_load_timeout(self.timeout)
       self.wait = WebDriverWait(self.browser, self.timeout)

   def __del__(self):
       self.browser.close()

   def process_request(self, request, spider):
       """
       用PhantomJS抓取页面
       :param request: Request对象
       :param spider: Spider对象
       :return: HtmlResponse
       """
       self.logger.debug('PhantomJS is Starting')
       page = request.meta.get('page', 1)
       try:
           self.browser.get(request.url)
           if page > 1:
               input = self.wait.until(
                   EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input')))
               submit = self.wait.until(
                   EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit')))
               input.clear()
               input.send_keys(page)
               submit.click()
           self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page)))
           self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
           return HtmlResponse(url=request.url, body=self.browser.page_source, request=request, encoding='utf-8', status=200)
       except TimeoutException:
           return HtmlResponse(url=request.url, status=500, request=request)

   @classmethod
   def from_crawler(cls, crawler):
       return cls(timeout=crawler.settings.get('SELENIUM_TIMEOUT'),
                  service_args=crawler.settings.get('PHANTOMJS_SERVICE_ARGS'))

首先我们在__init__()里对一些对象进行初始化,包括PhantomJSWebDriverWait等对象,同时设置页面大小和页面加载超时时间。在process_request()方法中,我们通过Request的meta属性获取当前需要爬取的页码,调用PhantomJS对象的get()方法访问Request的对应的URL。这就相当于从Request对象里获取请求链接,然后再用PhantomJS加载,而不再使用Scrapy里的Downloader。

随后的处理等待和翻页的方法在此不再赘述,和前文的原理完全相同。最后,页面加载完成之后,我们调用PhantomJS的page_source属性即可获取当前页面的源代码,然后用它来直接构造并返回一个HtmlResponse对象。构造这个对象的时候需要传入多个参数,如urlbody等,这些参数实际上就是它的基础属性。可以在官方文档查看HtmlResponse对象的结构:https://doc.scrapy.org/en/latest/topics/request-response.html。这样我们就成功利用PhantomJS来代替Scrapy完成了页面的加载,最后将Response返回即可。

 

有人可能会纳闷:为什么实现这么一个Downloader Middleware就可以了?之前的Request对象怎么办?Scrapy不再处理了吗?Response返回后又传递给了谁?

是的,Request对象到这里就不会再处理了,也不会再像以前一样交给Downloader下载。Response会直接传给Spider进行解析。

我们需要回顾一下Downloader Middleware的process_request()方法的处理逻辑,内容如下所示:

process_request()方法返回Response对象的时候,更低优先级的Downloader Middleware的process_request()process_exception()方法就不会被继续调用了,转而开始执行每个Downloader Middleware的process_response()方法,调用完毕之后直接将Response对象发送给Spider来处理。

这里直接返回了一个HtmlResponse对象,它是Response的子类,返回之后便顺次调用每个Downloader Middleware的process_response()方法。而在process_response()中我们没有对其做特殊处理,它会被发送给Spider,传给Request的回调函数进行解析。

到现在,我们应该能了解Downloader Middleware实现Selenium对接的原理了。

在settings.py里,我们设置调用刚才定义的SeleniumMiddleware,如下所示:

DOWNLOADER_MIDDLEWARES = {
   'scrapyseleniumtest.middlewares.SeleniumMiddleware': 543,
}

六、解析页面

Response对象就会回传给Spider内的回调函数进行解析。所以下一步我们就实现其回调函数,对网页来进行解析,代码如下所示:

def parse(self, response):
   products = response.xpath(
       '//div[@id="mainsrp-itemlist"]//div[@class="items"][1]//div[contains(@class, "item")]')
   for product in products:
       item = ProductItem()
       item['price'] = ''.join(product.xpath('.//div[contains(@class, "price")]//text()').extract()).strip()
       item['title'] = ''.join(product.xpath('.//div[contains(@class, "title")]//text()').extract()).strip()
       item['shop'] = ''.join(product.xpath('.//div[contains(@class, "shop")]//text()').extract()).strip()
       item['image'] = ''.join(product.xpath('.//div[@class="pic"]//img[contains(@class, "img")]/@data-src').extract()).strip()
       item['deal'] = product.xpath('.//div[contains(@class, "deal-cnt")]//text()').extract_first()
       item['location'] = product.xpath('.//div[contains(@class, "location")]//text()').extract_first()
       yield item

在这里我们使用XPath进行解析,调用response变量的xpath()方法即可。首先我们传递选取所有商品对应的XPath,可以匹配所有商品,随后对结果进行遍历,依次选取每个商品的名称、价格、图片等内容,构造并返回一个ProductItem对象。

七、存储结果

最后我们实现一个Item Pipeline,将结果保存到MongoDB,如下所示:

import pymongo

class MongoPipeline(object):
   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]

   def process_item(self, item, spider):
       self.db[item.collection].insert(dict(item))
       return item

   def close_spider(self, spider):
       self.client.close()

此实现和前文中存储到MongoDB的方法完全一致,原理不再赘述。记得在settings.py中开启它的调用,如下所示:

ITEM_PIPELINES = {
   'scrapyseleniumtest.pipelines.MongoPipeline': 300,
}

其中,MONGO_URIMONGO_DB的定义如下所示:

MONGO_URI = 'localhost'
MONGO_DB = 'taobao'

八、运行

整个项目就完成了,执行如下命令启动抓取即可:

scrapy crawl taobao

运行结果如下图所示。

查看MongoDB,结果如下图所示。

这样我们便成功在Scrapy中对接Selenium并实现了淘宝商品的抓取。

九、本节代码

本节代码地址为:https://github.com/Python3WebSpider/ScrapySeleniumTest。

十、结语

我们通过实现Downloader Middleware的方式实现了Selenium的对接。但这种方法其实是阻塞式的,也就是说这样就破坏了Scrapy异步处理的逻辑,速度会受到影响。为了不破坏其异步加载逻辑,我们可以使用Splash实现。

<think>好的,用户现在问的是关于Scrapy框架的介绍。先回想一下之前的对话历史,用户之前询问了构建生信数据库爬虫的技术栈,其中提到了Scrapy作为Python的爬虫框架。现在用户可能想更深入了解Scrapy的具体情况,比如它的结构、优势、适用场景等。 首先,用户可能对Scrapy的基本概念不太清楚,需要先解释它是什么。Scrapy是一个用Python编写的开源爬虫框架,用于高效地抓取网页数据,适合处理结构化数据。用户可能想知道它和其他工具比如Requests或Beautiful Soup的区别,所以需要强调Scrapy框架特性,比如内置的功能组件,而不仅仅是库。 接下来,用户可能想知道Scrapy的核心组件和工作流程。需要详细说明各个部分,比如Engine、Scheduler、Downloader、Spiders、Item Pipeline,以及它们如何协同工作。这部分内容有助于用户理解Scrapy的架构,为什么它适合大规模或复杂的爬取任务。 然后,应该提到Scrapy的优势,比如异步处理、中间件扩展、自动限速和内置数据导出。这些是用户选择Scrapy的主要原因,特别是处理大量数据时的效率和稳定性。同时,要说明适用场景,比如需要结构化数据、大规模爬取或需要遵守robots.txt的情况,以及不适合的场景,比如简单页面或需要执行JavaScript的情况。 用户可能还想知道如何开始使用Scrapy,所以提供一个简单的例子会很有帮助。展示创建项目、编写Spider、运行爬虫的步骤,让用户能够快速上手。此外,常见的扩展组件比如Selenium结合、中间件和Item Pipeline的用途,可以进一步展示Scrapy的灵活性。 最后,关联问题部分需要引导用户深入,比如如何结合动态页面处理或分布式爬虫,这可能暗示用户后续的问题方向。同时,确保回答结构清晰,分点说明,符合用户之前要求的格式。需要注意使用中文,避免Markdown,保持口语化,但又要专业准确。</think>Scrapy 是一个基于 Python 的 **开源网络爬虫框架**,专门设计用于高效、结构化地抓取网页数据并提取信息。以下是其核心特点与应用场景的详细介绍: --- ### **一、核心架构与工作流程** Scrapy 采用模块化设计,主要组件包括: 1. **引擎 (Engine)** 控制各组件间数据流,负责调度和通信。 2. **调度器 (Scheduler)** 管理待爬取的请求队列,支持优先级和去重。 3. **下载器 (Downloader)** 处理 HTTP 请求并返回响应,支持异步并发(默认使用 Twisted 库)。 4. **爬虫 (Spiders)** 用户自定义的解析逻辑,通过 `parse()` 方法提取数据和生成新请求。 5. **数据管道 (Item Pipeline)** 清洗、验证和存储数据(如写入数据库或文件)。 工作流程示意图: ``` Spider → 生成 Request → Engine → Scheduler → Downloader → 获取 Response → Spider 解析 → Item → Pipeline ``` --- ### **二、核心优势** 1. **异步非阻塞 IO** 基于 Twisted 的异步框架,可同时处理数百个请求,显著提升爬取效率。 2. **中间件扩展机制** 支持通过 Downloader Middlewares 和 Spider Middlewares 修改请求/响应(如添加代理、自定义 User-Agent)。 3. **自动限速与重试** 内置 `AutoThrottle` 扩展可动态调整请求频率,避免触发反爬机制。 4. **结构化数据输出** 通过 `Item` 类定义数据结构,支持导出 JSON、CSV 或直接对接数据库。 --- ### **三、典型应用场景** - **结构化数据采集** 如抓取生物信息数据库(NCBI、UniProt)中的基因序列、蛋白质结构数据。 - **大规模分布式爬虫** 结合 Scrapy-Redis 扩展可实现多节点分布式爬取。 - **遵守 Robots 协议** 默认开启 `ROBOTSTXT_OBEY` 设置,避免法律风险。 --- ### **四、快速入门示例** 1. **安装与创建项目** ```bash pip install scrapy scrapy startproject bio_crawler cd bio_crawler scrapy genspider example example.com ``` 2. **编写 Spider 代码** ```python import scrapy class ExampleSpider(scrapy.Spider): name = "example" start_urls = ["http://example.com/data"] def parse(self, response): # 提取数据并生成 Item yield { "gene_name": response.css("h1::text").get(), "sequence": response.xpath("//div[@class='dna']/text()").get() } ``` 3. **运行爬虫** ```bash scrapy crawl example -o output.json ``` --- ### **五、常见扩展与生态** - **动态页面处理** 结合 `scrapy-selenium` 或 `scrapy-playwright` 抓取 JavaScript 渲染的内容。 - **反爬对抗** 使用 `Rotating Proxies` 或 `Fake UserAgent` 中间件轮换 IP 和浏览器指纹。 - **数据存储** 通过 `Item Pipeline` 对接 MySQL、MongoDB 或云存储服务(如 AWS S3)。 --- ### **六、适用性对比** | 场景 | 适合使用 Scrapy | 不适合使用 Scrapy | |---------------------|------------------------|------------------------| | 数据规模 | 中大型、结构化数据 | 简单单页抓取 | | 复杂度 | 多层级链接跟踪 | 无需解析的静态内容 | | 动态内容 | 需配合无头浏览器 | 纯 JavaScript 渲染页面 | --- **-- 相关问题 --** 1. 如何将 ScrapySelenium 结合以抓取动态加载的生物数据库内容? 2. 使用 Scrapy-Redis 构建分布式爬虫时需要注意哪些关键配置? 3. 在生物信息学领域,Scrapy 的 Item Pipeline 如何优化数据清洗流程?
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值