我正在为Techcrunch,Bloomberg等新闻网站开发爬虫,所有这些网站都具有类似的延迟加载文章卡片的模式,只需单击"加载更多"类型的按钮。
我将其设计为使用multiprocessing
并行运行加载过程和摘要过程。对于上下文,下面的run
方法位于用于抽象不同站点元素的Crawler
类中,因此无需为每个站点编写抓取程序。这是输入方法:
def run(self):
""" Runs a crawler. """
binary: FirefoxBinary = FirefoxBinary(firefox_path="/usr/bin/firefox")
self.driver: Firefox = Firefox(firefox_binary=binary)
self.driver.get(self.url)
self.load_pipe, self.digest_pipe = Pipe()
load_proc: Process = Process(target=self._load_content)
load_proc.start()
digest_proc: Process = Process(target=self._digest_content)
digest_proc.start()
问题出现在加载过程中,在_load_content
方法中实现。特别是在第一行,随着find_element_by_class_name
的召唤.
def _load_content(self):
""" Loads more content. """
loader: WebElement = self.driver.find_element_by_class_name(self.loader_name)
...
在非并行中同步测试它时,该函数返回一个表示目标按钮的WebElement
。但是,当并行运行时,它返回一个表示整个页面正文的str
,然后抛出AttributeError: 'str' object has no attribute 'click'
。
我确保驱动程序在_load_content
中仍然完好无损,但该方法仍然返回str
而不是WebElement
。奇怪的是,如果没有找到具有给定类标识符的元素,它会引发NoSuchElementException
。那么为什么它将 HTML 正文作为str
返回呢?我错过了什么?multiprocessing
以某种方式弄乱驱动程序 API 吗?
由于浏览器本身的限制,WebDriver
API 不是线程安全的。浏览器一次需要一个命令,因此进程必须以非并行方式同步运行。即使您的资源可用于执行此操作,运行多个浏览器实例也无法解决问题,因为不会共享状态。
对此的一个潜在解决方案是在加载和摘要过程之间实现回调结构。像这样(伪代码(:
while article cards are available
digest article cards
if no article cards are available
load more article cards
start digesting article cards again
find_element_by_class_name
中的故障可能是由驱动程序实例的状态损坏或浏览器绑定无法按 API 预期的方式运行引起的。