文章目錄
- 爬取整個網站
- 反爬蟲
- 判别身份
- IP 限制
- robots.txt
爬取整個網站
為了爬取整個網站,我們得先分析該網站的資料是如何加載的。
還是以某瓣讀書為例,當我們點選第二頁後,觀察浏覽器的位址欄,發現網址變了。網址變成了 https://book.douban.com/top250?start=25,和原來相比後面多了一個 ?start=25。
這部分被稱為
查詢字元串
,查詢字元串作為用于搜尋的參數或處理的資料傳送給伺服器處理,格式是 ?key1=value1&key2=value2。
? 前面是網頁的位址,後面是查詢字元串。以鍵值對 key=value 的形式指派,多個鍵值對之間用 & 連接配接在一起。例如 ?a=1&b=2 表示:a 的值為 1,b 的值為 2。
查詢字元串用于資訊的傳遞,伺服器通過它就能知道你想要什麼,進而給你傳回對應的内容。比如你在知乎搜尋 python,網址會變成 https://www.zhihu.com/search?type=content&q=python,後面的查詢字元串告訴伺服器你想要的是有關 python 的内容,于是伺服器便将有關 python 的内容傳回給你了。
了解了查詢字元串的相關知識後,接下來我們多翻幾頁豆瓣讀書的頁面,觀察一下網址的變化規律:
第二頁 start=25,第三頁 start=50,第十頁 start=225,而每頁的書籍數量是 25。你有沒有發現其中的規律?
因為每頁展示 25 本書,根據規律其實不難推測出 start 參數表示從第幾本書開始展示,是以第一頁 start 是 0,第二頁 start 是 25,第三頁 start 是 50,第十頁 start 是 225。是以 start 的計算公式為 start = 25 * (頁碼數 - 1)(25 為每頁展示的數量)。我們來通過代碼自動生成某瓣圖書 Top250 所有資料(10 頁)的位址:
url = 'https://book.douban.com/top250?start={}'
# num 從 0 開始是以不用再 -1
urls = [url.format(num * 25) for num in range(10)]
print(urls)
# 輸出:
# [
# 'https://book.douban.com/top250?start=0',
# 'https://book.douban.com/top250?start=25',
# 'https://book.douban.com/top250?start=50',
# 'https://book.douban.com/top250?start=75',
# 'https://book.douban.com/top250?start=100',
# 'https://book.douban.com/top250?start=125',
# 'https://book.douban.com/top250?start=150',
# 'https://book.douban.com/top250?start=175',
# 'https://book.douban.com/top250?start=200',
# 'https://book.douban.com/top250?start=225'
# ]
有了所有網頁的連結後,我們就可以爬取整個網站的資料了!
我們可以修改代碼,将前文中的某瓣top250進行整個的爬取:
import requests
from bs4 import BeautifulSoup
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36 Edg/101.0.1210.32'
}
num = 0
# 為了對整個某瓣進行爬取,我們先對資料處理的部分進行封裝
def getTarget(url):
global num
#首先對網頁送出請求并獲得響應
req = requests.get(url,headers = headers)
#将網頁的源代碼形式解析
soup = BeautifulSoup(req.text,'html.parser')
#進行元素的第一次提取
result1 = soup.select('.item .pic')
for i in result1:
num += 1
name = i.select('a img')[0]['alt']
link = i.select('a')[0]['href']
print(num,' ',name,link)
for i in range(0,230,25):
address = 'https://movie.douban.com/top250?start={}'.format(i)
getTarget(address)
# 暫停 1 秒防止通路太快被封
time.sleep(1)
結果(不完整):
反爬蟲
反爬蟲
是網站限制爬蟲的一種政策。它并不是禁止爬蟲(完全禁止爬蟲幾乎不可能,也可能誤傷正常使用者),而是限制爬蟲,讓爬蟲在網站可接受的範圍内爬取資料,不至于導緻網站癱瘓無法運作。
常見的反爬蟲方式有
判别身份
和
IP 限制
兩種,我們一一介紹,并給出相應的反反爬蟲技巧。
判别身份
有些網站在識别出爬蟲後,會拒絕爬蟲的通路,比如某瓣。我們以某瓣圖書 Top250 為例,用浏覽器打開是下面這樣的:
如果用爬蟲直接爬取它,我們看看結果會是什麼:
import requests
res = requests.get('https://book.douban.com/top250')
print(res.text)
輸出結果為空,什麼都沒有。這是因為某瓣将我們的爬蟲識别了出來并拒絕提供内容。
你可能會有疑問,爬蟲不是模拟浏覽器通路網站、擷取網頁源代碼的嗎?怎麼會被識别出來呢?
其實,不管是浏覽器還是爬蟲,通路網站時都會帶上一些資訊用于身份識别。而這些資訊都被存儲在一個叫
請求頭(request headers)
的地方。
伺服器會通過請求頭裡的資訊來判别通路者的身份。請求頭裡的字段有很多,我們暫時隻需了解 user-agent(使用者代理)即可。user-agent 裡包含了作業系統、浏覽器類型、版本等資訊,通過修改它我們就能成功地僞裝成浏覽器。
我們可以根據下圖的訓示操作,看看請求頭長什麼樣:
我的浏覽器的 user-agent 是 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36,而 requests 預設的 user-agent 是 python-requests/2.22.0。
預設的 user-agent 和在頭上貼着“我是爬蟲”的紙條沒有什麼差別,難怪會被伺服器識别出來。是以我們需要修改請求頭裡的 user-agent 字段内容,将爬蟲僞裝成浏覽器。是以接下來我們來看看如何在 requests 庫中修改請求頭。
我們打開 requests 的官方文檔(http://cn.python-requests.org/zh_CN/latest/)并搜尋 定制請求頭,找到對應的文檔:
在 requests 的官方文檔中我們可以看到,我們隻需定義一個 字典(請求頭字段作為鍵,字段内容作為值)傳遞給 headers 參數即可。
除了 user-agent 之外的其他請求頭字段也能以同樣的方式添加進去,但大部分情況下我們隻需要添加 user-agent 字段即可(有些甚至不需要)。當我們加了 user-agent 字段還是無法擷取到資料時,說明該網站還通過别的資訊來驗證身份,我們可以将請求頭裡的字段都添加進去試試。
判别身份是最簡單的一種反爬蟲方式,我們也能通過一行代碼,将爬蟲僞裝成浏覽器輕易地繞過這個限制。是以,大部分網站還會進行 IP 限制 防止過于頻繁的通路。
IP 限制
IP(Internet Protocol)
全稱網際網路協定位址,意思是配置設定給使用者上網使用的網際協定的裝置的數字标簽。你可以将 IP 位址了解為門牌号,我隻要知道你家的門牌号就能找到你家。
IP 位址也是一樣,隻要知道伺服器的 IP 位址就能找到對應的伺服器。你在網上沖浪時電腦也會被配置設定一個 IP 位址,這樣伺服器就知道是誰在通路他們的網站了。
前面說過,當我們爬取大量資料時,如果我們不加以節制地通路目标網站,會使網站超負荷運轉,一些個人小網站沒什麼反爬蟲措施可能是以癱瘓。而大網站一般會限制你的通路頻率,因為正常人是不會在 1s 内通路幾十次甚至上百次網站的。
是以,如果你通路過于頻繁,即使改了 user-agent 僞裝成浏覽器了,也還是會被識别為爬蟲,并限制你的 IP 通路該網站。
是以,我們常常使用 time.sleep() 來降低通路的頻率,比如前文中的爬取整個網站的代碼,我們每爬取一個網頁就暫停一秒。這樣,對網站伺服器的壓力不會太大,對方也就睜一隻眼閉一隻眼不理會我們的爬蟲。雖然速度較慢,但也能擷取到我們想要的資料了。
除了降低通路頻率之外,我們也可以使用代理來解決 IP 限制的問題。代理的意思是通過别的 IP 通路網站。這樣,在 IP 被封後我們可以換一個 IP 繼續爬取資料,或者每次爬取資料時都換不同的 IP,避免同一個 IP 通路的頻率過高,這樣就能快速地大規模爬取資料了。
我們打開官方文檔,看看如何使用代理:
和 headers 一樣,也是定義一個字典,但傳遞給的是 proxies 參數。我們需要将 http 和 https 這兩種協定作為鍵,對應的 IP 代理作為值,最後将整個字典作為 proxies 參數傳遞給 requests.get() 方法即可。IP 代理有免費的和收費的,你可以自行在網上尋找。
提示:http 和 https 都是浏覽器和伺服器之間通訊的協定,https 更加的安全,也被越來越多的網站所使用。
官方文檔中給了代理的基本用法,但在爬取大量資料時我們需要很多的 IP 用于切換。是以,我們需要建立一個 IP 代理池(清單),每次從中随機選擇一個傳給 proxies 參數。我們來看一下如何實作:
import requests
import random
from bs4 import BeautifulSoup
def get_douban_books(url, proxies):
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
}
# 使用代理爬取資料
res = requests.get(url, proxies=proxies, headers=headers)
soup = BeautifulSoup(res.text, 'html.parser')
items = soup.find_all('div', class_='pl2')
for i in items:
tag = i.find('a')
name = tag['title']
link = tag['href']
print(name, link)
url = 'https://book.douban.com/top250?start={}'
urls = [url.format(num * 25) for num in range(10)]
# IP 代理池(瞎寫的并沒有用)
proxies_list = [
{
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
},
{
"http": "http://10.10.1.11:3128",
"https": "http://10.10.1.11:1080",
},
{
"http": "http://10.10.1.12:3128",
"https": "http://10.10.1.12:1080",
}
]
for i in urls:
# 從 IP 代理池中随機選擇一個
proxies = random.choice(proxies_list)
get_douban_books(i, proxies)
這樣,我們就能既快速又不會被限制地爬取資料了。但毫無節制的爬蟲已經和網絡攻擊沒什麼差別了,嚴重的甚至會導緻網站癱瘓。惡意地大量通路别人的網站,消耗伺服器資源是非常不道德、甚至違法的。
其實,除了反爬蟲措施遠不隻這兩種。比如驗證碼,當你的操作異常時網站經常會彈出驗證碼。驗證碼技術也在不斷地更新,從原先簡單的随機生成的數字字母,演變成随機漢字,再由按順序點選圖中對應的漢字演變成滑塊拼圖。
還有些驗證碼是一個數學題,你得計算出答案。像 12306 的驗證碼就更複雜了,需要識别出圖中的物體。驗證碼的形式千千萬萬,破解起來也較為困難。我們這裡不涉及。
了解了一些反爬蟲措施和反反爬蟲技巧後,我們來看看爬蟲中的君子協定—robots.txt。
robots.txt
robots.txt 是一種存放于網站根目錄下的文本檔案,用于告訴爬蟲此網站中的哪些内容是不應被爬取的,哪些是可以被爬取的。
我們隻要在網站域名後加上 /robots.txt 即可檢視,比如某瓣讀書的 robots.txt 位址是:https://book.douban.com/robots.txt。打開它後的内容如下:
User-agent: *
Disallow: /subject_search
Disallow: /search
Disallow: /new_subject
Disallow: /service/iframe
Disallow: /j/
User-agent: Wandoujia Spider
Disallow: /
User-agent: * 表示針對所有爬蟲(* 是通配符),接下來是符合該 user-agent 的爬蟲要遵守的規則。比如 Disallow: /search 表示禁止爬取 /search 這個頁面,其他同理。