Selenium及Headless Chrome抓取动态HTML页面
一般的的静态HTML页面可以使用requests等库直接抓取,但还有一部分比较复杂的动态页面,这些页面的DOM是动态生成的,有些还需要用户与其点击互动,这些页面只能使用真实的浏览器引擎动态解析,Selenium和Chrome Headless可以很好的达到这种目的。
Headless Chrome
Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有Chrome支持的特性,在命令行中运行你的脚本。以前在爬虫要使用Phantomjs来实现这些功能,但Phantomjs已经暂停开发,现在可以使用Headless Chrome来代替。
使用很简单,保证chrome命令指向chrome浏览器的安装路径,ubuntu下为google-chrome。
输出html:
google-chrome --headless --dump-dom https://www.cnblogs.com/
将目标页面截图:
google-chrome --headless --disable-gpu --screenshot https://www.cnblogs.com/ # 规定大小 google-chrome --headless --disable-gpu --screenshot --window-size=640,960 https://www.cnblogs.com/
保存为pdf:
google-chrome --headless --disable-gpu --print-to-pdf https://www.cnblogs.com/
以上文件会保存于当前目录。
还可以使用--remote-debugging-port参数进行远程调试:
google-chrome --headless --disable-gpu --no-sandbox --remote-debugging-port=9222 --user-data-dir='/d/cnblogs' http://www.cnblogs.com
--user-data-dir参数可以设定保存目录,--user-agent参数可以设定请求agent。上述的命令打开了一个websocket调试接口对当前Tab内页面的DOM、网络、性能、存储等等进行调试。
打开http://127.0.0.1:9222/链接可以看到可检查的网页,可以点击它们并看到使用了哪种Headless渲染。
还有一系列地址:
http://127.0.0.1:9222/json 查看已经打开的Tab列表:
[ { "description": "", "devtoolsFrontendUrl": "/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/5C7774203404DC082182AF4563CC7256", "id": "5C7774203404DC082182AF4563CC7256", "title": "博客园 - 代码改变世界", "type": "page", "url": "https://www.cnblogs.com/", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/5C7774203404DC082182AF4563CC7256" } ]
http://127.0.0.1:9222/json/version : 查看浏览器版本信息
{ "Browser": "HeadlessChrome/71.0.3578.98", "Protocol-Version": "1.3", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/71.0.3578.98 Safari/537.36", "V8-Version": "7.1.302.31", "WebKit-Version": "537.36 (@15234034d19b85dcd9a03b164ae89d04145d8368)", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser/ed156c0d-805c-4849-99d0-02e454260c17" }
http://127.0.0.1:9222/json/new?http://www.baidu.com : 新开Tab打开指定地址
http://127.0.0.1:9222/json/close/8795FFF09B01BD41B1F2931110475A67 : 关闭指定Tab,close后为tab页面的id
http://127.0.0.1:9222/json/activate/5C7774203404DC082182AF4563CC7256 : 切换到目标Tab
tab页面信息中有一个devtoolsFrontendUrl,是开发者工具的前端地址,可以打开:
http://127.0.0.1:9222/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/CE2E627C634EAAE3CE9193DC374C7B4A
在开发者工具里切换到Performance,勾选Screenshots,点刷新图标,重新加载完成就可以看到逐帧加载的截图:
Selenium
Selenium 是用于测试 Web 应用程序用户界面的常用框架,它支持各种浏览器,包括 Chrome,Safari,Firefox 等,支持多种语言开发,比如 Java,C,Ruby等等,当然也有Python。
pip install selenium
使用时还需要下载浏览器驱动,以chromedriver为例,下载地址:
国内镜像:
下载时注意与电脑的chrome版本保持一致,然后将chromedriver置于环境变量之中。
打开一个淘宝商品网页:
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://market.m.taobao.com/app/dinamic/h5-tb-detail/index.html?id=568217064643')
浏览器会自动打开并访问网页。
使用headless模式:
from selenium import webdriver chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') browser = webdriver.Chrome(options=chrome_options) browser.get('https://market.m.taobao.com/app/dinamic/h5-tb-detail/index.html?id=568217064643') data = browser.page_source
page_souce属性可以获取html网页源码。
可以看到获取的源码都是些js与css语句,dom并未生成,需要模拟浏览器滚动来生成dom:
for i in range(1, 11): browser.execute_script( "window.scrollTo(0, document.body.scrollHeight/10*%s);" % i ) time.sleep(0.5)
execute_script方法可以用来执行js脚本。
现在获取的源码基本是完整的,还存在一些小问题,比如网页为了让img延迟加载,img的地址是放在data-img属性上的,等到浏览器滑动至图片时才修改src属性,可以使用pyquery修改:
import time from selenium import webdriver from pyquery import PyQuery as pq base_dir = os.path.dirname(__file__) chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') browser = webdriver.Chrome(options=chrome_options) # browser.implicitly_wait(10) browser.get('https://market.m.taobao.com/app/dinamic/h5-tb-detail/index.html?id=568217064643') for i in range(1, 11): browser.execute_script( "window.scrollTo(0, document.body.scrollHeight/10*%s);" % i ) time.sleep(0.5) data = browser.page_source.encode('utf-8') doc = pq(data) for img in doc('img'): img = pq(img) if img.attr['data-img']: img.attr.src = img.attr['data-img'] data = doc.html(method='html').replace('src="//', 'src="http://') f = open(os.path.join(base_dir, 'detail.html'), 'w') f.write(data.encode('utf-8')) f.close()
保存为html后打开可以看到网页爬取成功。
selenium还提供了很多element提取接口:
提取单个element:
elem = browser.find_element_by_id("description")
提取多个:
elem = browser.find_elements_by_class_name("detail-desc")
批量爬取
可以使用concurrent.futures 线程池进行多线程批量爬取:
# -*- coding: utf-8 -*- import threading import time import os from concurrent.futures import ThreadPoolExecutor, as_completed from pyquery import PyQuery as pq class TaobaoCrawler(object): def __init__(self, ids): self.ids = ids self.browsers = {} self.timeout_spus = [] self.url = 'https://market.m.taobao.com/app/dinamic/h5-tb-detail/index.html?id=' def _create_new_browser(self): from selenium import webdriver chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') # chrome_options.add_argument('--blink-settings=imagesEnabled=false') browser = webdriver.Chrome(options=chrome_options) return browser def get_browser(self): current_thread_id = threading.currentThread().ident existed = self.browsers.get(current_thread_id) if existed: return existed new_browser = self._create_new_browser() self.browsers[current_thread_id] = new_browser return new_browser def close_browsers(self): for _, browser in self.browsers.iteritems(): browser.quit() self.browsers = {} def scroll_browser(self, browser, num): '''模拟浏览器滚动 保证js全部执行完成''' for i in range(1, num+1): browser.execute_script( "window.scrollTo(0, document.body.scrollHeight/%d*%d);" % ( num, i) ) time.sleep(0.5) def handle_detail_doc(self, detail): doc = pq(detail) for img in doc('img'): img = pq(img) if img.attr['data-img']: img.attr.src = img.attr['data-img'] detail = doc.html(method='html') detail = detail.replace('src="//', 'src="http://') return detail def crawl_taobao_detail(self, taobao_id): browser = self.get_browser() url = self.url + str(taobao_id) browser.execute_script("window.stop();") browser.get(url) self.scroll_browser(browser, 20) data = browser.page_source.encode('utf-8') data = self.handle_detail_doc(data) return taobao_id, data def start_crawl(self): if not self.ids: return with ThreadPoolExecutor(max_workers=4) as executor: futures = [executor.submit(self.crawl_taobao_detail, _) for _ in self.ids] for task in as_completed(futures): if task.done(): taobao_id, data = task.result() base_dir = os.path.dirname(__file__) f = open(os.path.join(base_dir, str(taobao_id) + '.html'), 'w') f.write(data.encode('utf-8')) f.close() self.close_browsers() def test_crawl(): ids = [568217064643, 584126060993, 581555053584, 581002124614] c = TaobaoCrawler(ids) c.start_crawl() if __name__ == '__main__': test_crawl()