Beautifulsoup和selenium的簡單使用
requests庫的複習
好久沒用requests了,因為一會兒要寫個簡單的爬蟲,所以還是隨便寫一點複習下。
import requestsr = requests.get('https://api.github.com/user', auth=('haiyu19931121@163.com', 'Shy18137803170'))print(r.status_code) # 狀態代碼200print(r.json()) # 返回json格式print(r.text) # 返迴文本print(r.headers) # 頭資訊print(r.encoding) # 編碼方式,一般utf-8# 當寫入檔案比較大時,避免記憶體耗盡,可以一次寫指定的位元組數或者一行。# 一次讀一行,chunk_size=512為預設值for chunk in r.iter_lines():print(chunk)# 一次讀取一塊,大小為512for chunk in r.iter_content(chunk_size=512):print(chunk)
注意iter_lines和iter_content返回的都是位元組資料,若要寫入檔案,不管是文本還是圖片,都需要以wb的方式開啟。
Beautifulsoup的使用
進入正題,早就聽說這個著名的庫,以前寫爬蟲用Regex雖然不麻煩,但有時候會匹配不準確。使用Beautifulsoup可以準確從HTML標籤中提取資料。雖然是慢了點,但是簡單好使呀。
from bs4 import BeautifulSouphtml_doc = """<html><head><title>The Dormouse's story</title></head><body><p class="title"><b>The Dormouse's story</b></p><p class="story">Once upon a time there were three little sisters; and their names were<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;and they lived at the bottom of a well.</p><p class="story">...</p>"""# 就注意一點,第二個參數指定解析器,必須填上,不然會有警告。推薦使用lxmlsoup = BeautifulSoup(html_doc, 'lxml')
緊接著上面的代碼,看下面一些簡單的操作。使用點屬性的行為,會得到第一個尋找到的合格資料。是find方法的簡寫。
soup.asoup.find('p')
上面的兩句是等價的。
# soup.body是一個Tag對象。是body標籤中所有html代碼print(soup.body)
<body><p class="title"><b>The Dormouse's story</b></p><p class="story">Once upon a time there were three little sisters; and their names were<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;and they lived at the bottom of a well.</p><p class="story">...</p></body>
# 擷取body裡所有文本,不含標籤print(soup.body.text)# 等同於下面的寫法soup.body.get_text()# 還可以這樣寫,strings是所有文本的產生器for string in soup.body.strings:print(string, end='')
The Dormouse's storyOnce upon a time there were three little sisters; and their names wereElsie,Lacie andTillie;and they lived at the bottom of a well....
# 獲得該標籤裡的文本。print(soup.title.string)
The Dormouse's story
# Tag對象的get方法可以根據屬性的名稱獲得屬性的值,此句表示得到第一個p標籤裡class屬性的值print(soup.p.get('class'))# 和下面的寫法等同print(soup.p['class'])
['title']
# 查看a標籤的所有屬性,以字典形式給出print(soup.a.attrs)
{'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'}
# 標籤的名稱soup.title.name
title
find_all
使用最多的當屬find_all / find方法了吧,前者尋找所有合格資料,返回一個列表。後者則是這個列表中的第一個資料。find_all有一個limit參數,限制列表的長度(即尋找合格資料的個數)。當limit=1其實就成了find方法 。
find_all同樣有簡寫方法。
soup.find_all('a', id='link1')soup('a', id='link1')
上面兩種寫法是等價的,第二種寫法便是簡寫。
find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
name
name就是想要搜尋的標籤,比如下面就是找到所有的p標籤。不僅能填入字串,還能傳入Regex、列表、函數、True。
# 傳入字串soup.find_all('p')# 傳入Regeximport re# 必須以b開頭for tag in soup.find_all(re.compile("^b")):print(tag.name)# body# b# 含有t就行for tag in soup.find_all(re.compile("t")):print(tag.name)# html# title# 傳入列表表示,一次尋找多個標籤soup.find_all(["a", "b"])# [<b>The Dormouse's story</b>,# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
傳入True的話,就沒有限制,什麼都尋找了。
recursive
調用tag的 find_all() 方法時,Beautiful Soup會檢索當前tag的所有子孫節點,如果只想搜尋tag的直接子節點,可以使用參數 recursive=False 。
# title不是html的直接子節點,但是會檢索其下所有子孫節點soup.html.find_all("title")# [<title>The Dormouse's story</title>]# 參數設定為False,只會找直接子節點soup.html.find_all("title", recursive=False)# []# title就是head的直接子節點,所以這個參數此時無影響a = soup.head.find_all("title", recursive=False)# [<title name="good">The Dormouse's story</title>]
keyword和attrs
使用keyword,加上一個或者多個限定條件,縮小尋找範圍。
# 查看所有id為link1的p標籤soup.find_all('a', id='link1')
如果按類尋找,由於class關鍵字Python已經使用。可以用class_,或者不指定關鍵字,又或者使用attrs填入字典。
soup.find_all('p', class_='story')soup.find_all('p', 'story')soup.find_all('p', attrs={"class": "story"})
上面三種方法等價。class_可以接受字串、Regex、函數、True。
text
搜尋文本值,好像使用string參數也是一樣的結果。
a = soup.find_all(text='Elsie')# 或者,4.4以上版本請使用texta = soup.find_all(string='Elsie')
text參數也可以接受字串、Regex、True、列表。
CSS選取器
還能使用CSS選取器呢。使用select方法就好了,select始終返回一個列表。
列舉幾個常用的操作。
# 所有div標籤soup.select('div')# 所有id為username的元素soup.select('.username')# 所有class為story的元素soup.select('#story')# 所有div元素之內的span元素,中間可以有其他元素soup.select('div span')# 所有div元素之內的span元素,中間沒有其他元素soup.select('div > span')# 所有具有一個id屬性的input標籤,id的值無所謂soup.select('input[id]')# 所有具有一個id屬性且值為user的input標籤soup.select('input[id="user"]')# 搜尋多個,class為link1或者link2的元素都符合soup.select("#link1, #link2")
一個爬蟲小例子
上面介紹了requests和beautifulsoup4的基本用法,使用這些已經可以寫一些簡單的爬蟲了。來試試吧。
此例子來自《Python編程快速上手——讓繁瑣的工作自動化》[美] AI Sweigart
這個爬蟲會批量下載XKCD漫畫網的圖片,可以指定下載的頁面數。
import osimport requestsfrom bs4 import BeautifulSoup# exist_ok=True,若檔案夾已經存在也不會報錯os.makedirs('xkcd')url = 'https://xkcd.com/'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/57.0.2987.98 Safari/537.36'}def save_img(img_url, limit=1): r = requests.get(img_url, headers=headers) soup = BeautifulSoup(r.text, 'lxml')try: img = 'https:' + soup.find('div', id='comic').img.get('src')except AttributeError:print('Image Not Found')else:print('Downloading', img) response = requests.get(img, headers=headers)with open(os.path.join('xkcd', os.path.basename(img)), 'wb') as f:for chunk in response.iter_content(chunk_size=1024*1024): f.write(chunk)# 每次下載一張圖片,就減1limit -= 1# 找到上一張圖片的網址if limit > 0:try: prev = 'https://xkcd.com' + soup.find('a', rel='prev').get('href')except AttributeError:print('Link Not Exist')else: save_img(prev, limit)if __name__ == '__main__': save_img(url, limit=20)print('Done!')
Downloading Downloading Downloading Downloading Downloading Downloading Downloading Downloading Downloading ...Done!
多線程下載
單線程的速度有點慢,比如可以使用多線程,由於我們在擷取prev的時候,知道了每個網頁的網址是很有規律的。它像這樣。只是最後的數字不一樣,所以我們可以很方便地使用range來遍曆。
import osimport threadingimport requestsfrom bs4 import BeautifulSoupos.makedirs('xkcd')headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/57.0.2987.98 Safari/537.36'}def download_imgs(start, end):for url_num in range(start, end): img_url = 'https://xkcd.com/' + str(url_num) r = requests.get(img_url, headers=headers) soup = BeautifulSoup(r.text, 'lxml')try: img = 'https:' + soup.find('div', id='comic').img.get('src')except AttributeError:print('Image Not Found')else:print('Downloading', img) response = requests.get(img, headers=headers)with open(os.path.join('xkcd', os.path.basename(img)), 'wb') as f:for chunk in response.iter_content(chunk_size=1024 * 1024): f.write(chunk)if __name__ == '__main__':# 下載從1到30,每個線程下載10個threads = []for i in range(1, 30, 10): thread_obj = threading.Thread(target=download_imgs, args=(i, i + 10)) threads.append(thread_obj) thread_obj.start()# 阻塞,等待線程執行結束都會等待for thread in threads: thread.join()# 所有線程下載完畢,才列印print('Done!')
來看下結果吧。
初步瞭解selenium
selenium用來作自動化測試。使用前需要下載驅動,我只下載了Firefox和Chrome的。網上隨便一搜就能下載到了。接下來將下載下來的檔案其複製到將安裝目錄下,比如Firefox,將對應的驅動程式放到C:\Program Files (x86)\Mozilla Firefox,並將這個路徑添加到環境變數中,同理Chrome的驅動程式放到C:\Program Files (x86)\Google\Chrome\Application並將該路徑添加到環境變數。最後重啟IDE開始使用吧。
類比百度搜尋
下面這個例子會開啟Chrome瀏覽器,訪問百度首頁,類比輸入The Zen of Python,隨後點擊百度一下,當然也可以用斷行符號代替。Keys下是一些不能用字串表示的鍵,比如方向鍵、Tab、Enter、Esc、F1~F12、Backspace等。然後等待3秒,頁面跳轉到知乎首頁,接著返回到百度,最後退出(關閉)瀏覽器。
from selenium import webdriverfrom selenium.webdriver.common.keys import Keysimport timebrowser = webdriver.Chrome()# Chrome開啟百度首頁browser.get('https://www.baidu.com/')# 找到輸入地區input_area = browser.find_element_by_id('kw')# 地區內填寫內容input_area.send_keys('The Zen of Python')# 找到"百度一下"search = browser.find_element_by_id('su')# 點擊search.click()# 或者按下斷行符號# input_area.send_keys('The Zen of Python', Keys.ENTER)time.sleep(3)browser.get('https://www.zhihu.com/')time.sleep(2)# 返回到百度搜尋browser.back()time.sleep(2)# 退出瀏覽器browser.quit()
send_keys類比輸入內容。可以使用element的clear()方法清空輸入。一些其他類比點擊瀏覽器按鈕的方法如下
browser.back() # 返回按鈕browser.forward() # 前進按鈕browser.refresh() # 重新整理按鈕browser.close() # 關閉當前視窗browser.quit() # 退出瀏覽器
尋找方法
以下列舉常用的尋找Element的方法。
| 方法名 |
返回的WebElement |
| find_element_by_id(id) |
匹配id屬性值的元素 |
| find_element_by_name(name) |
匹配name屬性值的元素 |
| find_element_by_class_name(name) |
匹配CSS的class值的元素 |
| find_element_by_tag_name(tag) |
匹配標籤名的元素,如div |
| find_element_by_css_selector(selector) |
匹配CSS選取器 |
| find_element_by_xpath(xpath) |
匹配xpath |
| find_element_by_link_text(text) |
完全符合提供的text的a標籤 |
| find_element_by_partial_link_text(text) |
提供的text可以是a標籤中文本中的一部分 |
登入CSDN
以下代碼可以類比輸入帳號密碼,點擊登入。整個過程還是很快的。
browser = webdriver.Chrome()browser.get('https://passport.csdn.net/account/login')browser.find_element_by_id('username').send_keys('haiyu19931121@163.com')browser.find_element_by_id('password').send_keys('**********')browser.find_element_by_class_name('logging').click()
以上差不多都是API的羅列,其中有自己的理解,也有照搬官方文檔的。
by @sunhaiyu
2017.7.13