我用python和selenium组合编写了一个脚本,从其登录页中抓取不同帖子的链接,并通过跟踪指向其内部页面的url最终获得每个帖子的标题。尽管我在这里解析的内容是静态的,但我使用了selenium来了解它在多处理中的工作原理。
然而,我的意图是使用多处理来进行刮擦。到目前为止,我知道硒不支持多处理,但似乎我错了。
我的问题是:当使用多处理运行硒时,如何减少使用硒的执行时间
This is my try (it's a working one)
:
import requests
from urllib.parse import urljoin
from multiprocessing.pool import ThreadPool
from bs4 import BeautifulSoup
from selenium import webdriver
def get_links(link):
res = requests.get(link)
soup = BeautifulSoup(res.text,"lxml")
titles = [urljoin(url,items.get("href")) for items in soup.select(".summary .question-hyperlink")]
return titles
def get_title(url):
chromeOptions = webdriver.ChromeOptions()
chromeOptions.add_argument("--headless")
driver = webdriver.Chrome(chrome_options=chromeOptions)
driver.get(url)
sauce = BeautifulSoup(driver.page_source,"lxml")
item = sauce.select_one("h1 a").text
print(item)
if __name__ == '__main__':
url = "https://stackoverflow.com/questions/tagged/web-scraping"
ThreadPool(5).map(get_title,get_links(url))
当使用多处理运行硒时,如何减少使用硒的执行时间
解决方案中的许多时间都花在为每个URL启动网络驱动程序上。你可以通过每个线程只启动一次驱动程序来减少这个时间:
(... skipped for brevity ...)
threadLocal = threading.local()
def get_driver():
driver = getattr(threadLocal, 'driver', None)
if driver is None:
chromeOptions = webdriver.ChromeOptions()
chromeOptions.add_argument("--headless")
driver = webdriver.Chrome(chrome_options=chromeOptions)
setattr(threadLocal, 'driver', driver)
return driver
def get_title(url):
driver = get_driver()
driver.get(url)
(...)
(...)
在我的系统中,这将时间从1m7秒减少到24.895秒,提高了约35%。要测试自己,请下载完整的脚本。
注意:ThreadPool
使用线程,这些线程受Python GIL的约束。如果大部分任务都是I/O绑定的,那也没关系。根据对刮取的结果进行的后处理,您可能需要使用multiprocessing.Pool
。这启动了作为一个组不受GIL约束的并行进程。代码的其余部分保持不变。
我看到的每个线程一个驱动程序的聪明答案的一个潜在问题是,它省略了";退出";驱动程序,从而留下了进程悬而未决的可能性。我会做以下更改:
- 改为使用
Driver
类,该类将封装驱动程序实例并将其存储在线程本地存储中,但也有一个析构函数,该析构函数将在删除线程本地存储时quit
驱动程序:
class Driver:
def __init__(self):
options = webdriver.ChromeOptions()
options.add_argument("--headless")
self.driver = webdriver.Chrome(options=options)
def __del__(self):
self.driver.quit() # clean up driver when we are cleaned up
#print('The driver has been "quitted".')
create_driver
现在变为:
threadLocal = threading.local()
def create_driver():
the_driver = getattr(threadLocal, 'the_driver', None)
if the_driver is None:
the_driver = Driver()
setattr(threadLocal, 'the_driver', the_driver)
return the_driver.driver
- 最后,在您不再使用
ThreadPool
实例之后,但在它终止之前,添加以下行以删除线程本地存储,并强制调用Driver
实例的析构函数(希望如此(:
del threadLocal
import gc
gc.collect() # a little extra insurance
我的问题是:如何减少执行时间?
Selenium似乎是错误的网络抓取工具——尽管我很欣赏YMMV,特别是如果你需要模拟用户与网站的交互,或者有一些JavaScript限制/要求。
对于没有太多交互的抓取任务,我使用开源的Scrapy-Python包进行大规模的抓取任务取得了很好的效果。它可以开箱即用地进行多处理,可以很容易地编写新脚本并将数据存储在文件或数据库中,而且速度非常快。
当您的脚本作为一个完全并行的Scrapy spider实现时,它会是这样的(注意,我没有测试它,请参阅有关选择器的文档(。
import scrapy
class BlogSpider(scrapy.Spider):
name = 'blogspider'
start_urls = ['https://stackoverflow.com/questions/tagged/web-scraping']
def parse(self, response):
for title in response.css('.summary .question-hyperlink'):
yield title.get('href')
要运行,请将其放入blogspider.py
并运行
$ scrapy runspider blogspider.py
请参阅Scrapy网站以获取完整的教程。
请注意,由于@SIM的指针,Scrapy还通过Scrapy splash支持JavaScript。到目前为止,我还没有接触过它,所以除了它看起来与Scrapy的工作原理很好地结合在一起之外,我无法谈论它。