pyppeteer:比 selenium 更高效的爬虫利器
?
API 接口文檔:API Reference:https://miyakogi.github.io/pyppeteer/reference.html
pyppeteer?github 地址:https://github.com/miyakogi/pyppeteer
pyppeteer??英文文檔地址:https://miyakogi.github.io/pyppeteer/
pyppeteer?官方文檔 API Reference :https://miyakogi.github.io/pyppeteer/reference.html
puppeteer( Nodejs 版 selenium )快速入門:https://blog.csdn.net/freeking101/article/details/91542887
python爬蟲利器 pyppeteer(模擬瀏覽器) 實(shí)戰(zhàn):https://blog.csdn.net/xiaoming0018/article/details/89841728
重點(diǎn):pyppeteer使用遇到的 bug 及解決方法:http://www.sanfenzui.com/pyppeteer-bug-collection.html
pyppeteer 進(jìn)階技巧 (?Xvfb 配合實(shí)現(xiàn) headless 效果 ):https://www.cnblogs.com/dyfblog/p/10887940.html
Python 爬蟲之pyppeteer 的使用(爬蟲、獲取cookie、截屏插件、防爬繞過):https://mohen.blog.csdn.net/article/details/107312709
爬蟲神器 Pyppeteer 的使用:https://blog.csdn.net/weixin_38819889/article/details/108684254
?
?
Pyppeteer 簡(jiǎn)介
?
提起 selenium 想必大家都不陌生,作為一款知名的 Web 自動(dòng)化測(cè)試框架,selenium 支持多款主流瀏覽器,提供了功能豐富的API 接口,經(jīng)常被我們用作爬蟲工具來使用。但是 selenium 的缺點(diǎn)也很明顯,比如速度太慢、對(duì)版本配置要求嚴(yán)苛,最麻煩是經(jīng)常要更新對(duì)應(yīng)的驅(qū)動(dòng)。還有些網(wǎng)頁是可以檢測(cè)到是否是使用了selenium?。并且selenium 所謂的保護(hù)機(jī)制不允許跨域 cookies 保存以及登錄的時(shí)候必須先打開網(wǎng)頁然后后加載 cookies 再刷新的方式很不友好。
今天就給大家介紹另一款 web 自動(dòng)化測(cè)試工具?Pyppeteer,雖然支持的瀏覽器比較單一,但在安裝配置的便利性和運(yùn)行效率方面都要遠(yuǎn)勝 selenium。
介紹 Pyppeteer 之前先說一下 Puppeteer,Puppeteer 是 Google 基于 Node.js 開發(fā)的一個(gè)工具,主要是用來操縱 Chrome? 瀏覽器的 API,通過 Javascript 代碼來操縱 Chrome 瀏覽器的一些操作,用作網(wǎng)絡(luò)爬蟲完成數(shù)據(jù)爬取、Web 程序自動(dòng)測(cè)試等任務(wù)。其 API 極其完善,功能非常強(qiáng)大。 而 Pyppeteer 又是什么呢?它實(shí)際上是 Puppeteer 的 Python 版本的實(shí)現(xiàn),但他不是 Google 開發(fā)的,是一位來自于日本的工程師依據(jù) Puppeteer 的一些功能開發(fā)出來的非官方版本。
Pyppeteer 其實(shí)是 Puppeteer 的 Python 版本。pyppeteer 模塊看不懂就去看puppeteer文檔,pyppeteer 只是在 puppeteer之上稍微包裝了下而已 。
注意:本來 chrome 就問題多多,puppeteer 也是各種坑,加上 pyppeteer 是基于前者的改編 python 版本,也就是產(chǎn)生了只要前兩個(gè)有一個(gè)有 bug,那么 pyppeteer 就會(huì)原封不動(dòng)的繼承下來,本來這沒什么,但是現(xiàn)在遇到的問題就是 pyppeteer 這個(gè)項(xiàng)目從2018年9月份之后幾乎沒更新過,前兩者都在不斷的更新迭代,而 pyppeteer 一直不更新,導(dǎo)致很多 bug 根本沒人修復(fù)。
?
下面簡(jiǎn)單介紹下 Pyppeteer 的兩大特點(diǎn):chromium 瀏覽器 和 asyncio框架:
?
1).chromium
Chromium 是一款獨(dú)立的瀏覽器,是 Google 為發(fā)展自家的瀏覽器 Google Chrome 而開啟的計(jì)劃,相當(dāng)于 Chrome的實(shí)驗(yàn)版,且 Chromium 是完全開源的。二者基于相同的源代碼構(gòu)建,Chrome 所有的新功能都會(huì)先在 Chromium 上實(shí)現(xiàn),待驗(yàn)證穩(wěn)定后才會(huì)移植,因此 Chromium 的版本更新頻率更高,也會(huì)包含很多新的功能,但作為一款獨(dú)立的瀏覽器,Chromium 的用戶群體要小眾得多。兩款瀏覽器“同根同源”,它們有著同樣的 Logo,但配色不同,Chrome 由藍(lán)紅綠黃四種顏色組成,而 Chromium 由不同深度的藍(lán)色構(gòu)成。
Pyppeteer 的 web 自動(dòng)化是基于 chromium 來實(shí)現(xiàn)的,由于 chromium 中某些特性的關(guān)系,Pyppeteer 的安裝配置非常簡(jiǎn)單,關(guān)于這一點(diǎn)稍后我們會(huì)詳細(xì)介紹。
?
2).asyncio
asyncio 是 Python 的一個(gè)異步協(xié)程庫(kù),自3.4版本引入的標(biāo)準(zhǔn)庫(kù),直接內(nèi)置了對(duì)異步IO的支持,號(hào)稱是Python最有野心的庫(kù),官網(wǎng)上有非常詳細(xì)的介紹:https://docs.python.org/3/library/asyncio.html
?
?
安裝與使用
?
由于 Pyppeteer 采用了 Python 的 async 機(jī)制,所以其運(yùn)行要求的 Python 版本為 3.5 及以上。
?
1).極簡(jiǎn)安裝
使用 pip3 install pyppeteer 命令就能完成 pyppeteer 庫(kù)的安裝,至于 chromium 瀏覽器,只需要一條 pyppeteer-install 命令就會(huì)自動(dòng)下載對(duì)應(yīng)的最新版本 chromium 瀏覽器到 pyppeteer 的默認(rèn)位置。
window 下 安裝完?pyppeteer ,會(huì)在 python 安裝目錄下的?Scripts 目錄下 有?pyppeteer-install.exe 和?pyppeteer-install-script.py 兩個(gè)文件,執(zhí)行 任意一個(gè)都可以安裝?chromium 瀏覽器到 pyppeteer 的默認(rèn)位置。
運(yùn)行?pyppeteer-install.exe :
如果不運(yùn)行 pyppeteer-install 命令,在第一次使用 pyppeteer 的時(shí)候也會(huì)自動(dòng)下載并安裝 chromium 瀏覽器,效果是一樣的。總的來說,pyppeteer 比起 selenium 省去了 driver 配置的環(huán)節(jié)。
當(dāng)然,出于某種原因(需要梯子,或者科學(xué)上網(wǎng)),也可能會(huì)出現(xiàn)chromium自動(dòng)安裝無法順利完成的情況,這時(shí)可以考慮手動(dòng)安裝:首先,從下列網(wǎng)址中找到自己系統(tǒng)的對(duì)應(yīng)版本,下載chromium壓縮包;
'linux': 'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/575458/chrome-linux.zip'
'mac': 'https://storage.googleapis.com/chromium-browser-snapshots/Mac/575458/chrome-mac.zip'
'win32': 'https://storage.googleapis.com/chromium-browser-snapshots/Win/575458/chrome-win32.zip'
'win64': 'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/575458/chrome-win32.zip'
然后,將壓縮包放到pyppeteer的指定目錄下解壓縮,windows系統(tǒng)的默認(rèn)目錄。
其他系統(tǒng)下的默認(rèn)目錄可以參照下面:
- Windows:?C:\Users\<username>\AppData\Local\pyppeteer
- OS X:?/Users/<username>/Library/Application?Support/pyppeteer
- Linux:?/home/<username>/.local/share/pyppeteer
- or in?$XDG_DATA_HOME/pyppeteer?if?$XDG_DATA_HOME?is defined.
Details see?appdirs’s?user_data_dir.
好了,安裝完成之后我們命令行下測(cè)試下:
>>> import pyppeteer
如果沒有報(bào)錯(cuò),那么就證明安裝成功了。
?
2).使用
Pyppeteer 是一款非常高效的 web 自動(dòng)化測(cè)試工具,由于 Pyppeteer 是基于 asyncio 構(gòu)建的,它的所有 屬性 和方法 幾乎都是 coroutine (協(xié)程) 對(duì)象,因此在構(gòu)建異步程序的時(shí)候非常方便,天生就支持異步運(yùn)行。
程序構(gòu)建的基本思路是新建 一個(gè) browser 瀏覽器 和 一個(gè) 頁面 page。
看下面這段代碼,在 main 函數(shù)中,先是建立一個(gè)瀏覽器對(duì)象,然后打開新的標(biāo)簽頁,訪問百度主頁,對(duì)當(dāng)前頁面截圖并保存為“example.png”,最后關(guān)閉瀏覽器。前文也提到過,pyppeteer 是基于 asyncio 構(gòu)建的,所以在使用的時(shí)候需要用到 async/await 結(jié)構(gòu)。
import asyncio from pyppeteer import launchasync def main():browser = await launch()page = await browser.newPage()await page.goto('http://baidu.com')await page.screenshot({'path': 'example.png'})await browser.close()asyncio.get_event_loop().run_until_complete(main())運(yùn)行上面這段代碼會(huì)發(fā)現(xiàn)并沒有瀏覽器彈出運(yùn)行,這是因?yàn)?Pyppeteer 默認(rèn)使用的是無頭瀏覽器,如果想要瀏覽器顯示,需要在launch 函數(shù)中設(shè)置參數(shù) “headless =False”,程序運(yùn)行結(jié)束后在同一目錄下會(huì)出現(xiàn)截取到的網(wǎng)頁圖片:
?
遇到的錯(cuò)誤
?
- 1)pyppeteer.errors.NetworkError: Protocol error Network.getCookies: Target close
控制訪問指定 url 之后 await page.goto(url),會(huì)遇到上面的錯(cuò)誤,如果這時(shí)候使用了 sleep 之類的延時(shí)也會(huì)出現(xiàn)這個(gè)錯(cuò)誤或者類似的 time out。?
這個(gè)問題是 puppeteer 的 bug,但是對(duì)方已經(jīng)修復(fù)了,而 pyppeteer 遲遲沒更新,就只能靠自己了,搜了很多人的文章,例如:https://github.com/miyakogi/pyppeteer/issues/171 ,但是我按照這個(gè)并沒有成功。也有人增加一個(gè)函數(shù),但調(diào)用這個(gè)參數(shù)依然沒解決問題。
可以把 python 第三方庫(kù) websockets 版本 7.0 改為 6.0 就可以了,親測(cè)可用。
pip uninstall websockets #卸載websocketspip install websockets==6.0 或者 pip install websockets==6.0 --force-reinstall #指定安裝6.0版本?
- 2)chromium瀏覽器多開頁面卡死問題。
解決這個(gè)問題的方法就是瀏覽器初始化的時(shí)候添加'dumpio':True。
# 啟動(dòng) pyppeteer 屬于內(nèi)存中實(shí)現(xiàn)交互的模擬器 browser = await launch({'headless': False, 'args': ['--no-sandbox'], 'dumpio': True})- 3)瀏覽器窗口很大,內(nèi)容顯示很小。
需要設(shè)置瀏覽器顯示大小,默認(rèn)就是無法正常顯示。可以看到頁面左側(cè)右側(cè)都是空白,網(wǎng)站內(nèi)容并沒有完整鋪滿chrome.
# Pyppeteer 支持字典 和 關(guān)鍵字傳參,Puppeteer 只支持字典傳參。 # 這里使用字典傳參 browser = await launch({'headless': False, 'dumpio': True, 'autoClose': False, 'args': ['--no-sandbox', '--window-size=1366,850']} ) await page.setViewport({'width': 1366, 'height': 768})通過上面設(shè)置Windows-size和Viewport大小來實(shí)現(xiàn)網(wǎng)頁完整顯示。
但是對(duì)于那種向下無限加載的長(zhǎng)網(wǎng)頁這種情況如果瀏覽器是可見狀態(tài)會(huì)顯示不全,針對(duì)這種情況的解決方法就是復(fù)制當(dāng)前網(wǎng)頁新開一個(gè)標(biāo)簽頁粘貼進(jìn)去就正常了
?
Pyppeteer 和 Puppeteer 的 不同點(diǎn)
- Pyppeteer支持字典和關(guān)鍵字傳參,Puppeteer只支持字典傳參
- 元素選擇器方法名 $變?yōu)閝uerySelector
- Page.evaluate() 和 Page.querySelectorEval()的參數(shù)
Puppeteer的evaluate()方法使用JavaScript原生函數(shù)或JavaScript表達(dá)式字符串。Pyppeteer的evaluate()方法只使用JavaScript字符串,該字符串可以是函數(shù)也可以是表達(dá)式,Pyppeteer會(huì)進(jìn)行自動(dòng)判斷。但有時(shí)會(huì)判斷錯(cuò)誤,如果字符串被判斷成了函數(shù),并且報(bào)錯(cuò),可以添加選項(xiàng)force_expr=True,強(qiáng)制Pyppeteer作為表達(dá)式處理。
獲取頁面內(nèi)容:
content = await page.evaluate('document.body.textContent', force_expr=True)獲取元素的內(nèi)部文字:
element = await page.querySelector('h1') title = await page.evaluate('(element) => element.textContent', element)?
基礎(chǔ)用法
?
抓取內(nèi)容 ?可以使用 xpath 表達(dá)式
"""
# Pyppeteer 三種解析方式
? ? Page.querySelector()? ? ? # 選擇器
? ? Page.querySelectorAll()
? ? Page.xpath()? ? ? ? ? ? ? ? ? ?# xpath ?表達(dá)式
# 簡(jiǎn)寫方式為:
? ? Page.J(), Page.JJ(), and Page.Jx()
"""
示例 1 :
import asyncio from pyppeteer import launchasync def main():# headless參數(shù)設(shè)為False,則變成有頭模式# Pyppeteer支持字典和關(guān)鍵字傳參,Puppeteer只支持字典傳參# 指定引擎路徑# exepath = r'C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer\local-chromium\575458\chrome-win32/chrome.exe'# browser = await launch({'executablePath': exepath, 'headless': False, 'slowMo': 30})browser = await launch(# headless=False,{'headless': False})page = await browser.newPage()# 設(shè)置頁面視圖大小await page.setViewport(viewport={'width': 1280, 'height': 800})# 是否啟用JS,enabled設(shè)為False,則無渲染效果await page.setJavaScriptEnabled(enabled=True)# 超時(shí)間見 1000 毫秒res = await page.goto('https://www.toutiao.com/', options={'timeout': 1000})resp_headers = res.headers # 響應(yīng)頭resp_status = res.status # 響應(yīng)狀態(tài)# 等待await asyncio.sleep(2)# 第二種方法,在while循環(huán)里強(qiáng)行查詢某元素進(jìn)行等待while not await page.querySelector('.t'):pass# 滾動(dòng)到頁面底部await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')await asyncio.sleep(2)# 截圖 保存圖片await page.screenshot({'path': 'toutiao.png'})# 打印頁面cookiesprint(await page.cookies())""" 打印頁面文本 """# 獲取所有 html 內(nèi)容print(await page.content())# 在網(wǎng)頁上執(zhí)行js 腳本dimensions = await page.evaluate(pageFunction='''() => {return {width: document.documentElement.clientWidth, // 頁面寬度height: document.documentElement.clientHeight, // 頁面高度deviceScaleFactor: window.devicePixelRatio, // 像素比 1.0000000149011612}}''', force_expr=False) # force_expr=False 執(zhí)行的是函數(shù)print(dimensions)# 只獲取文本 執(zhí)行 js 腳本 force_expr 為 True 則執(zhí)行的是表達(dá)式content = await page.evaluate(pageFunction='document.body.textContent', force_expr=True)print(content)# 打印當(dāng)前頁標(biāo)題print(await page.title())# 抓取新聞內(nèi)容 可以使用 xpath 表達(dá)式"""# Pyppeteer 三種解析方式Page.querySelector() # 選擇器Page.querySelectorAll()Page.xpath() # xpath 表達(dá)式# 簡(jiǎn)寫方式為:Page.J(), Page.JJ(), and Page.Jx()"""element = await page.querySelector(".feed-infinite-wrapper > ul>li") # 紙抓取一個(gè)print(element)# 獲取所有文本內(nèi)容 執(zhí)行 jscontent = await page.evaluate('(element) => element.textContent', element)print(content)# elements = await page.xpath('//div[@class="title-box"]/a')elements = await page.querySelectorAll(".title-box a")for item in elements:print(await item.getProperty('textContent'))# <pyppeteer.execution_context.JSHandle object at 0x000002220E7FE518># 獲取文本title_str = await (await item.getProperty('textContent')).jsonValue()# 獲取鏈接title_link = await (await item.getProperty('href')).jsonValue()print(title_str)print(title_link)# 關(guān)閉瀏覽器await browser.close()asyncio.get_event_loop().run_until_complete(main())示例 2 :
import asyncio import pyppeteer from collections import namedtupleheaders = {'date': 'Sun, 28 Apr 2019 06:50:20 GMT','server': 'Cmcc','x-frame-options': 'SAMEORIGIN\nSAMEORIGIN','last-modified': 'Fri, 26 Apr 2019 09:58:09 GMT','accept-ranges': 'bytes','cache-control': 'max-age=43200','expires': 'Sun, 28 Apr 2019 18:50:20 GMT','vary': 'Accept-Encoding,User-Agent','content-encoding': 'gzip','content-length': '19823','content-type': 'text/html','connection': 'Keep-alive','via': '1.1 ID-0314217270751344 uproxy-17' }Response = namedtuple("rs", "title url html cookies headers history status")async def get_html(url):browser = await pyppeteer.launch(headless=True, args=['--no-sandbox'])page = await browser.newPage()res = await page.goto(url, options={'timeout': 10000})data = await page.content()title = await page.title()resp_cookies = await page.cookies() # cookieresp_headers = res.headers # 響應(yīng)頭resp_status = res.status # 響應(yīng)狀態(tài)print(data)print(title)print(resp_headers)print(resp_status)return titleif __name__ == '__main__':url_list = ["https://www.toutiao.com","http://jandan.net/ooxx/page-8#comments","https://www.12306.cn/index"]task = [get_html(url) for url in url_list]loop = asyncio.get_event_loop()results = loop.run_until_complete(asyncio.gather(*task))for res in results:print(res)?
模擬輸入
模擬輸入文本:
# 模擬輸入 賬號(hào)密碼 {'delay': rand_int()} 為輸入時(shí)間 await page.type('#TPL_username_1', "sadfasdfasdf") await page.type('#TPL_password_1', "123456789", )await page.waitFor(1000) await page.click("#J_SubmitStatic")使用 tkinter 獲取頁面高度 寬度
def screen_size():"""使用tkinter獲取屏幕大小"""import tkintertk = tkinter.Tk()width = tk.winfo_screenwidth()height = tk.winfo_screenheight()tk.quit()return width, height?
爬取京東商城
示例代碼:
import requests from bs4 import BeautifulSoup from pyppeteer import launch import asynciodef screen_size():"""使用tkinter獲取屏幕大小"""import tkintertk = tkinter.Tk()width = tk.winfo_screenwidth()height = tk.winfo_screenheight()tk.quit()return width, heightasync def main(url):# browser = await launch({'headless': False, 'args': ['--no-sandbox'], })browser = await launch({'args': ['--no-sandbox'], })page = await browser.newPage()width, height = screen_size()await page.setViewport(viewport={"width": width, "height": height})await page.setJavaScriptEnabled(enabled=True)await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299')await page.goto(url)# await asyncio.sleep(2)await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')await asyncio.sleep(1)# content = await page.content()li_list = await page.xpath('//*[@id="J_goodsList"]/ul/li')# print(li_list)item_list = []for li in li_list:a = await li.xpath('.//div[@class="p-img"]/a')detail_url = await (await a[0].getProperty("href")).jsonValue()promo_words = await (await a[0].getProperty("title")).jsonValue()a_ = await li.xpath('.//div[@class="p-commit"]/strong/a')p_commit = await (await a_[0].getProperty("textContent")).jsonValue()i = await li.xpath('./div/div[3]/strong/i')price = await (await i[0].getProperty("textContent")).jsonValue()em = await li.xpath('./div/div[4]/a/em')title = await (await em[0].getProperty("textContent")).jsonValue()item = {"title": title,"detail_url": detail_url,"promo_words": promo_words,'p_commit': p_commit,'price': price}item_list.append(item)# print(item)# break# print(content)await page_close(browser)return item_listasync def page_close(browser):for _page in await browser.pages():await _page.close()await browser.close()msg = "手機(jī)" url = "https://search.jd.com/Search?keyword={}&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq={}&cid2=653&cid3=655&page={}"task_list = [] for i in range(1, 6):page = i * 2 - 1url = url.format(msg, msg, page)task_list.append(main(url))loop = asyncio.get_event_loop() results = loop.run_until_complete(asyncio.gather(*task_list)) # print(results, len(results)) for i in results:print(i, len(i))print('*' * 100) # soup = BeautifulSoup(content, 'lxml') # div = soup.find('div', id='J_goodsList') # for i, li in enumerate(div.find_all('li', class_='gl-item')): # if li.select('.p-img a'): # print(li.select('.p-img a')[0]['href'], i) # print(li.select('.p-price i')[0].get_text(), i) # print(li.select('.p-name em')[0].text, i) # else: # print("#" * 200) # print(li)?
抓取淘寶
示例代碼:
# -*- coding: utf-8 -*-import time import random import asyncio from retrying import retry # 錯(cuò)誤自動(dòng)重試 from pyppeteer.launcher import launchjs1 = '''() =>{Object.defineProperties(navigator,{ webdriver:{ get: () => false}})}''' js2 = '''() => {alert(window.navigator.webdriver)}''' js3 = '''() => {window.navigator.chrome = {runtime: {}, }; }''' js4 = '''() =>{Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});}''' js5 = '''() =>{Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5,6],});}'''def retry_if_result_none(result):return result is None@retry(retry_on_result=retry_if_result_none, ) async def mouse_slide(page=None):await asyncio.sleep(3)try:await page.hover('#nc_1_n1z')await page.mouse.down()await page.mouse.move(2000, 0, {'delay': random.randint(1000, 2000)})await page.mouse.up()except Exception as e:print(e, ' :slide login False')return Noneelse:await asyncio.sleep(3)slider_again = await page.Jeval('.nc-lang-cnt', 'node => node.textContent')if slider_again != '驗(yàn)證通過':return Noneelse:await page.screenshot({'path': './headless-slide-result.png'})print('驗(yàn)證通過')return 1def input_time_random():return random.randint(100, 151)def screen_size():"""使用tkinter獲取屏幕大小"""import tkintertk = tkinter.Tk()width = tk.winfo_screenwidth()height = tk.winfo_screenheight()tk.quit()return width, heightasync def main(username, pwd, url):browser = await launch({'headless': False, 'args': ['--no-sandbox'], },userDataDir='./userdata',args=['--window-size=1366,768'])page = await browser.newPage()width, height = screen_size()await page.setViewport(viewport={"width": width, "height": height})await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299')await page.goto(url)await page.evaluate(js1)await page.evaluate(js3)await page.evaluate(js4)await page.evaluate(js5)pwd_login = await page.querySelector('.J_Quick2Static')# print(await (await pwd_login.getProperty('textContent')).jsonValue())await pwd_login.click()await page.type('#TPL_username_1', username, {'delay': input_time_random() - 50})await page.type('#TPL_password_1', pwd, {'delay': input_time_random()})await page.screenshot({'path': './headless-test-result.png'})time.sleep(2)slider = await page.Jeval('#nocaptcha', 'node => node.style') # 是否有滑塊if slider:print('出現(xiàn)滑塊情況判定')await page.screenshot({'path': './headless-login-slide.png'})flag = await mouse_slide(page=page)if flag:print(page.url)await page.keyboard.press('Enter')await get_cookie(page)else:await page.keyboard.press('Enter')await page.waitFor(20)await page.waitForNavigation()try:global errorerror = await page.Jeval('.error', 'node => node.textContent')except Exception as e:error = Noneprint(e, "錯(cuò)啦")finally:if error:print('確保賬戶安全重新入輸入')else:print(page.url)# 可繼續(xù)網(wǎng)頁跳轉(zhuǎn) 已經(jīng)攜帶 cookie# await get_search(page)await get_cookie(page)await page_close(browser)async def page_close(browser):for _page in await browser.pages():await _page.close()await browser.close()async def get_search(page):# https://s.taobao.com/search?q={查詢的條件}&p4ppushleft=1%2C48&s={每頁 44 條 第一頁 0 第二頁 44}&sort=sale-descawait page.goto("https://s.taobao.com/search?q=氣球")await asyncio.sleep(5)# print(await page.content())# 獲取登錄后cookie async def get_cookie(page):res = await page.content()cookies_list = await page.cookies()cookies = ''for cookie in cookies_list:str_cookie = '{0}={1};'str_cookie = str_cookie.format(cookie.get('name'), cookie.get('value'))cookies += str_cookieprint(cookies)# 將cookie 放入 cookie 池 以便多次請(qǐng)求 封賬號(hào) 利用cookie 對(duì)搜索內(nèi)容進(jìn)行爬取return cookiesif __name__ == '__main__':tb_username = '淘寶用戶名'tb_pwd = '淘寶密碼'tb_url = "https://login.taobao.com/member/login.jhtml"loop = asyncio.get_event_loop()loop.run_until_complete(main(tb_username, tb_pwd, tb_url))?
利用 上面 獲取到的 cookie 爬取搜索內(nèi)容
示例代碼:
import json import requests import re# 設(shè)置 cookie 池 隨機(jī)發(fā)送請(qǐng)求 通過 pyppeteer 獲取 cookie cookie = '_tb_token_=edd7e354dee53;t=fed8f4ca1946ca1e73223cfae04bc589;sg=20f;cna=2uJSFdQGmDMCAbfFWXWAC4Jv;cookie2=1db6cd63ad358170ea13319f7a862c33;_l_g_=Ug%3D%3D;v=0;unb=3150916610;skt=49cbfd5e01d1b550;cookie1=BxVRmD3sh19TaAU6lH88bHw5oq%2BgcAGcRe229Hj5DTA%3D;csg=cf45a9e2;uc3=vt3=F8dByEazRMnQZDe%2F9qI%3D&id2=UNGTqfZ61Z3rsA%3D%3D&nk2=oicxO%2BHX4Pg%3D&lg2=U%2BGCWk%2F75gdr5Q%3D%3D;existShop=MTU1Njg3MDM3MA%3D%3D;tracknick=%5Cu7433150322;lgc=%5Cu7433150322;_cc_=V32FPkk%2Fhw%3D%3D;mt=ci=86_1;dnk=%5Cu7433150322;_nk_=%5Cu7433150322;cookie17=UNGTqfZ61Z3rsA%3D%3D;tg=0;enc=tThHs6Sn3BAl8v1fu3J4tMpgzA1n%2BLzxjib0vDAtGsXJCb4hqQZ7Z9fHIzsN0WghdcKEsoeKz6mBwPUpyzLOZw%3D%3D;JSESSIONID=B3F383B3467EC60F8CA425935232D395;l=bBMspAhrveV5732DBOCanurza77OSIRYYuPzaNbMi_5pm6T_G4QOlC03xF96VjfRswYBqh6Mygv9-etuZ;hng=CN%7Czh-CN%7CCNY%7C156;isg=BLi41Q8PENDal3xUVsA-aPbfiWaKiRzB6vcTu_IpBPOmDVj3mjHsO86vxUQYW9SD;uc1=cookie16=W5iHLLyFPlMGbLDwA%2BdvAGZqLg%3D%3D&cookie21=W5iHLLyFeYZ1WM9hVnmS&cookie15=UIHiLt3xD8xYTw%3D%3D&existShop=false&pas=0&cookie14=UoTZ4ttqLhxJww%3D%3D&tag=8&lng=zh_CN;thw=cn;x=e%3D1%26p%3D*%26s%3D0%26c%3D0%26f%3D0%26g%3D0%26t%3D0;swfstore=34617;'headers = {'cookie': cookie,"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36" }rep = requests.get('https://s.taobao.com/search?q=手機(jī)&p4ppushleft=1%2C48&s=0&sort=sale-desc ', headers=headers) rep.encoding = 'utf-8' res = rep.text print(res)r = re.compile(r'g_page_config = (.*?)g_srp_loadCss', re.S) res = r.findall(res)data = res[0].strip().rstrip(';') dic_data = json.loads(data) auctions = dic_data.get('mods')['itemlist']['data']['auctions']# print(auctions,len(auctions)) for item in auctions[1:]:print(item)break?
針對(duì)iframe 的操作
- page.frames 獲取所有的 iframe 列表 需要判斷操作的是哪一個(gè) iframe 跟操作 page 一樣操作
?
與 scrapy 的整合
加入downloadmiddleware
from scrapy import signals from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware import random import pyppeteer import asyncio import os from scrapy.http import HtmlResponsepyppeteer.DEBUG = False class FundscrapyDownloaderMiddleware(object):# Not all methods need to be defined. If a method is not defined,# scrapy acts as if the downloader middleware does not modify the# passed objects.def __init__(self) :print("Init downloaderMiddleware use pypputeer.")os.environ['PYPPETEER_CHROMIUM_REVISION'] ='588429'# pyppeteer.DEBUG = Falseprint(os.environ.get('PYPPETEER_CHROMIUM_REVISION'))loop = asyncio.get_event_loop()task = asyncio.ensure_future(self.getbrowser())loop.run_until_complete(task)#self.browser = task.result()print(self.browser)print(self.page)# self.page = await browser.newPage()async def getbrowser(self):self.browser = await pyppeteer.launch()self.page = await self.browser.newPage()# return await pyppeteer.launch()async def getnewpage(self): return await self.browser.newPage()@classmethoddef from_crawler(cls, crawler):# This method is used by Scrapy to create your spiders.s = cls()crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)return sdef process_request(self, request, spider):# Called for each request that goes through the downloader# middleware.# Must either:# - return None: continue processing this request# - or return a Response object# - or return a Request object# - or raise IgnoreRequest: process_exception() methods of# installed downloader middleware will be calledloop = asyncio.get_event_loop()task = asyncio.ensure_future(self.usePypuppeteer(request))loop.run_until_complete(task)# return task.result()return HtmlResponse(url=request.url, body=task.result(), encoding="utf-8",request=request)async def usePypuppeteer(self, request):print(request.url)# page = await self.browser.newPage()await self.page.goto(request.url)content = await self.page.content()return content def process_response(self, request, response, spider):# Called with the response returned from the downloader.# Must either;# - return a Response object# - return a Request object# - or raise IgnoreRequestreturn responsedef process_exception(self, request, exception, spider):# Called when a download handler or a process_request()# (from other downloader middleware) raises an exception.# Must either:# - return None: continue processing this exception# - return a Response object: stops process_exception() chain# - return a Request object: stops process_exception() chainpassdef spider_opened(self, spider):spider.logger.info('Spider opened: %s' % spider.name)?
?
實(shí)戰(zhàn) 異步爬取
?
示例 1 :快速上手
接下來我們測(cè)試下基本的頁面渲染操作,這里我們選用的網(wǎng)址為:http://quotes.toscrape.com/js/,這個(gè)頁面是 JavaScript 渲染而成的,用基本的 requests 庫(kù)請(qǐng)求得到的 HTML 結(jié)果里面是不包含頁面中所見的條目?jī)?nèi)容的。
為了證明 requests 無法完成正常的抓取,我們可以先用如下代碼來測(cè)試一下:
import requests from pyquery import PyQuery as pqurl = 'http://quotes.toscrape.com/js/' response = requests.get(url=url) doc = pq(response.text) print('Quotes : {0}'.format(doc('.quote').length))# 結(jié)果 # Quotes : 0這里首先使用 requests 來請(qǐng)求網(wǎng)頁內(nèi)容,然后使用 pyquery 來解析頁面中的每一個(gè)條目。觀察源碼之后我們發(fā)現(xiàn)每個(gè)條目的 class 名為 quote,所以這里選用了 .quote 這個(gè) CSS 選擇器來選擇,最后輸出條目數(shù)量。
運(yùn)行結(jié)果:Quotes: 0
結(jié)果是 0,這就證明使用 requests 是無法正常抓取到相關(guān)數(shù)據(jù)的。
為什么?
因?yàn)檫@個(gè)頁面是 JavaScript 渲染而成的,我們所看到的內(nèi)容都是網(wǎng)頁加載后又執(zhí)行了 JavaScript 之后才呈現(xiàn)出來的,因此這些條目數(shù)據(jù)并不存在于原始 HTML 代碼中,而 requests 僅僅抓取的是原始 HTML 代碼。
好的,所以遇到這種類型的網(wǎng)站我們應(yīng)該怎么辦呢?
其實(shí)答案有很多:
而 Pyppeteer 和 Selenium 就是用的第三種方法,下面我們?cè)儆?Pyppeteer 來試試,如果用 Pyppeteer 實(shí)現(xiàn)如上頁面的抓取的話,代碼就可以寫為如下形式:
import asyncio from pyppeteer import launch from pyquery import PyQuery as pqasync def main():browser = await launch()page = await browser.newPage()url = 'http://quotes.toscrape.com/js/'await page.goto(url=url)doc = pq(await page.content())print('Quotes : {0}'.format(doc('.quote').length))await browser.close()asyncio.get_event_loop().run_until_complete(main())運(yùn)行結(jié)果:Quotes: 10
看運(yùn)行結(jié)果,這說明我們就成功匹配出來了 class 為 quote 的條目,總數(shù)為 10 條,具體的內(nèi)容可以進(jìn)一步使用 pyquery 解析查看。
那么這里面的過程發(fā)生了什么?
實(shí)際上,Pyppeteer 整個(gè)流程就完成了瀏覽器的開啟、新建頁面、頁面加載等操作。另外 Pyppeteer 里面進(jìn)行了異步操作,所以需要配合 async/await 關(guān)鍵詞來實(shí)現(xiàn)。首先, launch 方法會(huì)新建一個(gè) Browser 對(duì)象,然后賦值給 browser,然后調(diào)用 newPage 方法相當(dāng)于瀏覽器中新建了一個(gè)選項(xiàng)卡,同時(shí)新建了一個(gè) Page 對(duì)象。然后 Page 對(duì)象調(diào)用了 goto 方法就相當(dāng)于在瀏覽器中輸入了這個(gè) URL,瀏覽器跳轉(zhuǎn)到了對(duì)應(yīng)的頁面進(jìn)行加載,加載完成之后再調(diào)用 content 方法,返回當(dāng)前瀏覽器頁面的源代碼。然后進(jìn)一步地,我們用 pyquery 進(jìn)行同樣地解析,就可以得到 JavaScript 渲染的結(jié)果了。另外其他的一些方法如調(diào)用 asyncio 的 get_event_loop 等方法的相關(guān)操作則屬于 Python 異步 async 相關(guān)的內(nèi)容了,大家如果不熟悉可以了解下 Python 的 async/await 的相關(guān)知識(shí)。好,通過上面的代碼,我們就可以完成 JavaScript 渲染頁面的爬取了。
?
模擬網(wǎng)頁截圖,保存 PDF,執(zhí)行自定義的 JavaScript 獲得特定的內(nèi)容
接下來我們?cè)倏纯戳硗庖粋€(gè)例子,這個(gè)例子可以模擬網(wǎng)頁截圖,保存 PDF,另外還可以執(zhí)行自定義的 JavaScript 獲得特定的內(nèi)容,代碼如下:
import asyncio from pyppeteer import launchasync def main():browser = await launch()page = await browser.newPage()url = 'http://quotes.toscrape.com/js/'await page.goto(url=url)await page.screenshot(path='test_screenshot.png')await page.pdf(path='test_pdf.pdf')# 在網(wǎng)頁上執(zhí)行js 腳本dimensions = await page.evaluate(pageFunction='''() => {return {width: document.documentElement.clientWidth, // 頁面寬度height: document.documentElement.clientHeight, // 頁面高度deviceScaleFactor: window.devicePixelRatio, // 像素比 1.0000000149011612}}''', force_expr=False) # force_expr=False 執(zhí)行的是函數(shù)print(dimensions)await browser.close()asyncio.get_event_loop().run_until_complete(main())# 結(jié)果 # {'width': 800, 'height': 600, 'deviceScaleFactor': 1}這里我們又用到了幾個(gè)新的 API,完成了網(wǎng)頁截圖保存、網(wǎng)頁導(dǎo)出 PDF 保存、執(zhí)行 JavaScript 并返回對(duì)應(yīng)數(shù)據(jù)。
?evaluate 方法執(zhí)行了一些 JavaScript,JavaScript 傳入的是一個(gè)函數(shù),使用 return 方法返回了網(wǎng)頁的寬高、像素大小比率三個(gè)值,最后得到的是一個(gè) JSON 格式的對(duì)象。
總之利用 Pyppeteer 我們可以控制瀏覽器執(zhí)行幾乎所有動(dòng)作,想要的操作和功能基本都可以實(shí)現(xiàn),用它來自由地控制爬蟲當(dāng)然就不在話下了。
了解了基本的實(shí)例之后,我們?cè)賮硎崂硪幌?Pyppeteer 的一些基本和常用操作。Pyppeteer 的幾乎所有功能都能在其官方文檔的 API Reference 里面找到,鏈接為:https://miyakogi.github.io/pyppeteer/reference.html,用到哪個(gè)方法就來這里查詢就好了,參數(shù)不必死記硬背,即用即查就好。
?
登錄淘寶 (打開網(wǎng)頁后,手動(dòng)輸入用戶名和密碼,可以看到正常跳轉(zhuǎn)到登錄后的頁面):
import asyncio from pyppeteer import launchwidth, height = 1366, 768js1 = '''() =>{Object.defineProperties(navigator,{ webdriver:{ get: () => false}})}''' js2 = '''() => {alert(window.navigator.webdriver)}''' js3 = '''() => {window.navigator.chrome = {runtime: {}, }; }''' js4 = '''() =>{Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});}''' js5 = '''() =>{Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5,6],});}'''async def page_evaluate(page):# 替換淘寶在檢測(cè)瀏覽時(shí)采集的一些參數(shù)# 需要注意,在測(cè)試的過程中發(fā)現(xiàn)登陸成功后頁面的該屬性又會(huì)變成True# 所以在每次重新加載頁面后要重新設(shè)置該屬性的值。await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {}, }; }''')await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')async def main():browser = await launch(headless=False,# userDataDir='./userdata',args=['--disable-infobars', f'--window-size={width},{height}', '--no-sandbox'])page = await browser.newPage()await page.setViewport({"width": width,"height": height})url = 'https://www.taobao.com'await page.goto(url=url)# await page.evaluate(js1)# await page.evaluate(js3)# await page.evaluate(js4)# await page.evaluate(js5)await page_evaluate(page)await asyncio.sleep(100)# await browser.close()asyncio.get_event_loop().run_until_complete(main())如果把上面 js 去掉,發(fā)現(xiàn)淘寶可以檢測(cè)出來, 跳轉(zhuǎn)不到登錄后的頁面。
window.navigator 對(duì)象包含有關(guān)訪問者瀏覽器的信息:https://www.runoob.com/js/js-window-navigator.html
js 主要需要修改瀏覽器的 window.navigator.webdriver、window.navigator.languages等值。
打開正常的瀏覽器可以看到:
window.navigator.webdriver的值為undefined,而通過pyppeteer控制打開的瀏覽器該值為True,當(dāng)被檢測(cè)到該值為True的時(shí)候,則滑動(dòng)會(huì)一直失敗,所以我們需要修改該屬性。需要注意,在測(cè)試的過程中發(fā)現(xiàn)登陸成功后頁面的該屬性又會(huì)變成True,所以在每次重新加載頁面后要重新設(shè)置該屬性的值。
async def page_evaluate(page):# 替換淘寶在檢測(cè)瀏覽時(shí)采集的一些參數(shù)await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {}, }; }''')await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')?
另一種方法可以進(jìn)一步免去淘寶登錄的煩惱,那就是設(shè)置用戶目錄。
平時(shí)我們已經(jīng)注意到,當(dāng)我們登錄淘寶之后,如果下次再次打開瀏覽器發(fā)現(xiàn)還是登錄的狀態(tài)。這是因?yàn)樘詫毜囊恍╆P(guān)鍵 Cookies 已經(jīng)保存到本地了,下次登錄的時(shí)候可以直接讀取并保持登錄狀態(tài)。
那么這些信息保存在哪里了呢?其實(shí)就是保存在用戶目錄下了,里面不僅包含了瀏覽器的基本配置信息,還有一些 Cache、Cookies 等各種信息都在里面,如果我們能在瀏覽器啟動(dòng)的時(shí)候讀取這些信息,那么啟動(dòng)的時(shí)候就可以恢復(fù)一些歷史記錄甚至一些登錄狀態(tài)信息了。
這也就解決了一個(gè)問題:很多朋友在每次啟動(dòng) Selenium 或 Pyppeteer 的時(shí)候總是是一個(gè)全新的瀏覽器,那就是沒有設(shè)置用戶目錄,如果設(shè)置了它,每次打開就不再是一個(gè)全新的瀏覽器了,它可以恢復(fù)之前的歷史記錄,也可以恢復(fù)很多網(wǎng)站的登錄信息。
當(dāng)然可能時(shí)間太久了,Cookies 都過期了,那還是需要登錄的。
那么這個(gè)怎么來做呢?很簡(jiǎn)單,在啟動(dòng)的時(shí)候設(shè)置 userDataDir 就好了,示例如下:
browser = await launch(headless=False,userDataDir='./userdata',args=['--disable-infobars', f'--window-size={width},{height}'])好,這里就是加了一個(gè) userDataDir 的屬性,值為 userdata,即當(dāng)前目錄的 userdata 文件夾。我們可以首先運(yùn)行一下,然后登錄一次淘寶,這時(shí)候我們同時(shí)可以觀察到在當(dāng)前運(yùn)行目錄下又多了一個(gè) userdata 的文件夾:
用戶文件夾
具體的介紹可以看官方的一些說明,如:https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md?這里面介紹了 userdatadir 的相關(guān)內(nèi)容。
?
命令行啟動(dòng) chrome 并進(jìn)入指定的 URL:chrome.exe --disable-infobars --user-data-dir="./userdatadir" --new-window https://login.taobao.com/member/login.jhtml
執(zhí)行完后會(huì)打開 淘寶的登錄頁面,登錄淘寶,然后保存用戶名密碼,這樣登錄信息就保存在?userdatadir 目錄下了
在執(zhí)行?chrome.exe --disable-infobars --user-data-dir="./userdatadir" --new-window https://www.taobao.com
可以看到已經(jīng)時(shí)登錄狀態(tài)了。
?
?
?
示例 2 :爬取今日頭條
# -*- coding: utf-8 -*- # @Author : # @File : toutiao.py # @Software: PyCharm # @description : XXXimport asyncio from pyppeteer import launch from pyquery import PyQuery as pqdef screen_size():"""使用tkinter獲取屏幕大小"""import tkintertk = tkinter.Tk()width = tk.winfo_screenwidth()height = tk.winfo_screenheight()tk.quit()return width, heightasync def main():width, height = screen_size()print(f'screen : [ width:{width} , height:{height} ]')browser = await launch(headless=False, args=[f'--window-size={width},{height}'])page = await browser.newPage()await page.setViewport({'width': width, 'height': height})# 是否啟用JS,enabled設(shè)為False,則無渲染效果await page.setJavaScriptEnabled(enabled=True)await page.goto('https://www.toutiao.com')await asyncio.sleep(5)print(await page.cookies()) # 打印頁面cookiesprint(await page.content()) # 打印頁面文本print(await page.title()) # 打印當(dāng)前頁標(biāo)題# 抓取新聞標(biāo)題title_elements = await page.xpath('//div[@class="title-box"]/a')for item in title_elements:# 獲取文本title_str = await (await item.getProperty('textContent')).jsonValue()print(await item.getProperty('textContent'))# 獲取鏈接title_link = await (await item.getProperty('href')).jsonValue()print(title_str)print(title_link)# 在搜索框中輸入pythonawait page.type('input.tt-input__inner', 'python')# 點(diǎn)擊搜索按鈕await page.click('button.tt-button')await asyncio.sleep(5)# print(page.url)# 今日頭條點(diǎn)擊后新開一個(gè)頁面, 通過打印url可以看出page還停留在原頁面# 以下用于切換至新頁面pages = await browser.pages()page = pages[-1]# print(page.url)page_source = await page.content()text = pq(page_source)await page.goto(url="https://www.toutiao.com/api/search/content/?""aid=24&app_name=web_search&offset=60&format=json""&keyword=python&autoload=true&count=20&en_qc=1""&cur_tab=1&from=search_tab&pd=synthesis×tamp=1555589585193")for i in range(1, 10):print(text("#J_section_{} > div > div > div.normal.rbox > div > div.title-box > a > span".format(i)).text())# 關(guān)閉瀏覽器await browser.close()asyncio.get_event_loop().run_until_complete(main())示例代碼2:
import asyncio from pyppeteer import launchasync def main():# headless參數(shù)設(shè)為False,則變成有頭模式browser = await launch(# headless=False)page = await browser.newPage()# 設(shè)置頁面視圖大小await page.setViewport(viewport={'width': 1280, 'height': 800})# 是否啟用JS,enabled設(shè)為False,則無渲染效果await page.setJavaScriptEnabled(enabled=True)await page.goto('https://www.toutiao.com/')# 打印頁面cookiesprint(await page.cookies())# 打印頁面文本print(await page.content())# 打印當(dāng)前頁標(biāo)題print(await page.title())# 抓取新聞標(biāo)題title_elements = await page.xpath('//div[@class="title-box"]/a')for item in title_elements:# 獲取文本title_str = await (await item.getProperty('textContent')).jsonValue()print(await item.getProperty('textContent'))# 獲取鏈接title_link = await (await item.getProperty('href')).jsonValue()print(title_str)print(title_link)# 關(guān)閉瀏覽器await browser.close()asyncio.get_event_loop().run_until_complete(main())?
?百度首頁交互
示例代碼:
import time import asyncio from pyppeteer import launchasync def main():browser = await launch(headless=False)page = await browser.newPage()await page.setViewport({'width': 1200, 'height': 800})await page.goto('https://www.baidu.com')# 在搜索框中輸入pythonawait page.type('input#kw.s_ipt','python')# 點(diǎn)擊搜索按鈕await page.click('input#su')# 等待元素加載,第一種方法,強(qiáng)行等待5秒# await asyncio.sleep(5)# 第二種方法,在while循環(huán)里強(qiáng)行查詢某元素進(jìn)行等待while not await page.querySelector('.t'):pass# 滾動(dòng)到頁面底部await page.evaluate('window.scrollBy(0, window.innerHeight)')# 這些等待方法都不好用# await page.waitForXPath('h3', timeout=300)# await page.waitForNavigation(waitUntil="networkidle0")# await page.waitForFunction('document.getElementByTag("h3")')# await page.waitForSelector('.t')# await page.waitFor('document.querySelector("#t")')# await page.waitForNavigation(waitUntil='networkidle0')# await page.waitForFunction('document.querySelector("").inner??Text.length == 7')title_elements = await page.xpath('//h3[contains(@class,"t")]/a')for item in title_elements:title_str = await (await item.getProperty('textContent')).jsonValue()print(title_str)await browser.close()asyncio.get_event_loop().run_until_complete(main())?
示例:
import asyncio import pyppeteer from collections import namedtupleResponse = namedtuple("rs", "title url html cookies headers history status")async def get_html(url, timeout=30):# 默認(rèn)30sbrowser = await pyppeteer.launch(headless=True, args=['--no-sandbox'])page = await browser.newPage()res = await page.goto(url, options={'timeout': int(timeout * 1000)})data = await page.content()title = await page.title()resp_cookies = await page.cookies()resp_headers = res.headersresp_history = Noneresp_status = res.statusresponse = Response(title=title,url=url,html=data,cookies=resp_cookies,headers=resp_headers,history=resp_history,status=resp_status)return responseif __name__ == '__main__':url_list = ["http://www.10086.cn/index/tj/index_220_220.html","http://www.10010.com/net5/011/",# "http://python.jobbole.com/87541/"]task = (get_html(url) for url in url_list)loop = asyncio.get_event_loop()results = loop.run_until_complete(asyncio.gather(*task))for res in results:print(res.title)?
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的pyppeteer:比 selenium 更高效的爬虫利器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Xvfb 虚拟现实库 之 Python
- 下一篇: 应用层(知识架构图)