
初識爬蟲
學習爬蟲之前,我們首先得了解什么是爬蟲。
來自於百度百科的解釋:
網絡爬蟲(又稱為網頁蜘蛛,網絡機器人,在FOAF社區中間,更經常的稱為網頁追逐者),是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本。
通俗來講,假如你需要互聯網上的信息,如商品價格,圖片視頻資源等,但你又不想或者不能自己一個一個自己去打開網頁收集,這時候你便寫了一個程序,讓程序按照你指定好的規則去互聯網上收集信息,這便是爬蟲,我們熟知的百度,谷歌等搜索引擎背后其實也是一個巨大的爬蟲。
爬蟲合法嗎?
可能很多小伙伴都會又這個疑問,首先爬蟲是一門技術,技術應該是中立的,合不合法其實取決於你使用目的,是由爬蟲背后的人來決定的,而不是爬蟲來決定的。另外我們爬取信息的時候也可以稍微‘克制’一下,能拿到自己想要的信息就夠了,沒必要對着人家一直擼,看看我們的12306都被逼成啥樣了🤧🤧🤧。
一般來說只要不影響人家網站的正常運轉,也不是出於商業目的,人家一般也就只會封下的IP,賬號之類的,不至於法律風險👌。
其實大部分網站都會有一個robots協議,在網站的根目錄下會有個robots.txt的文件,里面寫明了網站里面哪些內容可以抓取,哪些不允許。
以淘寶為例——https://www.taobao.com/robots.txt

當然robots協議本身也只是一個業內的約定,是不具有法律意義的,所以遵不遵守呢也只能取決於用戶本身的底線了。
Why Python
很多人提到爬蟲就會想到Python,其實除了Python,其他的語言諸如C,PHP,Java等等都可以寫爬蟲,而且一般來說這些語言的執行效率還要比Python要高,但為什么目前來說,Python漸漸成為了寫很多人寫爬蟲的第一選擇,我簡單總結了以下幾點:
- 開發效率高,代碼簡潔,一行代碼就可完成請求,100行可以完成一個復雜的爬蟲任務;
- 爬蟲對於代碼執行效率要求不高,網站IO才是最影響爬蟲效率的。如一個網頁請求可能需要100ms,數據處理10ms還是1ms影響不大;
- 非常多優秀的第三方庫,如requests,beautifulsoup,selenium等等;
本文后續內容也將會以Python作為基礎來進行講解。
環境准備
- Python安裝,這部分可以參考我之前的文章Python環境配置&Pycharm安裝,去官網下載對應的安裝包,一路Next安裝就行了;
- pip安裝,pip是Python的包管理器,現在的Python安裝包一般都會自帶pip,不需要自己再去額外安裝了;
- requests,beautifulsoup庫的安裝,通過以下語句來完成安裝:
pip install requests
pip install beautifulsoup4 - 谷歌瀏覽器(chrome);
第三方庫介紹
requests
requests應該是用Python寫爬蟲用到最多的庫了,同時requests也是目前Github上star✨最多的Python開源項目。
requests在爬蟲中一般用於來處理網絡請求,接下來會用通過簡單的示例來展示requests的基本用法。
- 首先我們需要倒入
requests模塊;
import requests
- 接着我們嘗試向baidu發起請求;
r = requests.get('https://www.baidu.com/')
- 我們現在獲得來命名為
r的response對象,從這個對象中我們便可以獲取到很多信息,如:
- 狀態碼,
200即為請求成功 - 頁面Html5代碼
# 返回請求狀態碼,200即為請求成功
print(r.status_code)
# 返回頁面代碼
print(r.text)
# 對於特定類型請求,如Ajax請求返回的json數據
print(r.json())
- 當然對於大部分網站都會需要你表明你的身份,我們一般正常訪問網站都會附帶一個請求頭(headers)信息,里面包含了你的瀏覽器,編碼等內容,網站會通過這部分信息來判斷你的身份,所以我們一般寫爬蟲也加上一個headers;
# 添加headers
headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'}
r = requests.get('https://www.baidu.com/', headers=headers)
- 針對
post請求,也是一樣簡單;
# 添加headers
headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'}
# post請求
data = {'users': 'abc', 'password': '123'}
r = requests.post('https://www.weibo.com', data=data, headers=headers)
- 很多時候等於需要登錄的站點我們可能需要保持一個會話,不然每次請求都先登錄一遍效率太低,在
requests里面一樣很簡單;
# 保持會話
# 新建一個session對象
sess = requests.session()
# 先完成登錄
sess.post('maybe a login url', data=data, headers=headers)
# 然后再在這個會話下去訪問其他的網址
sess.get('other urls')
beautifulsoup
當我們通過requests獲取到整個頁面的html5代碼之后,我們還得進一步處理,因為我們需要的往往只是整個頁面上的一小部分數據,所以我們需要對頁面代碼html5解析然后篩選提取出我們想要對數據,這時候beautifulsoup便派上用場了。
beautifulsoup之后通過標簽+屬性的方式來進行定位,譬如說我們想要百度的logo,我們查看頁面的html5代碼,我們可以發現logo圖片是在一個div的標簽下,然后class=index-logo-srcnew這個屬性下。

所以我們如果需要定位logo圖片的話便可以通過div和class=index-logo-srcnew來進行定位。
下面也會提供一些簡單的示例來說明beautifulsoup的基本用法:
- 導入beautifulsou模塊;
from bs4 import BeautifulSoup
- 對頁面代碼進行解析,這邊選用對html代碼是官方示例中使用的愛麗絲頁面代碼;
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><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>
"""
# 選用lxml解析器來解析
soup = BeautifulSoup(html, 'lxml')
- 我們現在獲得一個命名為
soup的Beautifulsoup對象,從這個對象中我們便能定位出我們想要的信息,如:
# 獲取標題
print(soup.title)
# 獲取文本
print(soup.title.text)
# 通過標簽定位
print(soup.find_all('a'))
# 通過屬性定位
print(soup.find_all(attrs={'id': 'link1'}))
# 標簽 + 屬性定位
print(soup.find_all('a', id='link1'))
打印結果如下:
<title>The Dormouse's story</title>
The Dormouse's story
[<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>]
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
案例分享
獲取17173新游頻道下游戲名
- 定位我們所需要的信息,記住html里面的位置。
這邊可以分享一個小技巧,以前我剛開始寫爬蟲的時候,尋找代碼里面的信息都是先去把整個頁面給down下來,然后再在里面Ctrl+F查找,其實大部分瀏覽器都提供了很簡單的方法來定位頁面代碼位置的,這邊會以Chrome瀏覽器為例。
為了方便理解錄制了一個gif,具體步驟如下:
- F12打開控制台,選擇
element標簽查看頁面代碼; - 點擊控制台左上角箭頭,然后點擊頁面上我們需要的信息,我們可以看到控制台中頁面代碼直接跳轉到對應的位置;
- 頁面代碼中一直向上選擇標簽直至囊括我們需要的所有信息;
- 記住此時的標簽以及熟悉等信息,這將會用於后面解析篩選數據。

- 接下來便可以開始敲代碼了,完整代碼如下,對於每個步驟均有詳細的注釋:
from bs4 import BeautifulSoup
import requests
# 頁面url地址
url = 'http://newgame.17173.com/game-list-0-0-0-0-0-0-0-0-0-0-1-2.html'
# 發送請求,r為頁面響應
r = requests.get(url)
# r.text獲取頁面代碼
# 使用lxml解析頁面代碼
soup = BeautifulSoup(r.text, 'lxml')
# 兩次定位,先找到整個信息區域
info_list = soup.find_all(attrs={'class': 'ptlist ptlist-pc'})
# 在此區域內獲取游戲名,find_all返回的是list
tit_list = info_list[0].find_all(attrs={'class': 'tit'})
# 遍歷獲取游戲名
# .text可獲取文本內容,替換掉文章中的換行符
for title in tit_list:
print(title.text.replace('\n', ''))
獲取拉勾網職位信息
目前很多網站上的信息都是通過Ajax動態加載的,譬如當你翻看某電商網站的評論,當你點擊下一頁的時候,網址並沒發生變化,但上面的評論都變了,這其實就是通過Ajax動態加載出來的。
這里的下一頁➡️按鈕並不是只想另外一個頁面,而是會在后台發送一個請求,服務器接收到這個請求之后會在當前頁面上渲染出來。
其實我自己是比較偏愛爬這種類型的數據的,因為統計Ajax請求返回來的數據都是非常規整的json數據,不需要我們去寫復雜的表達式去解析了。
接下來我們將會通過一個拉勾網職位信息的爬蟲來說明這類網站的爬取流程:
- F12打開控制台,然后搜索‘數據分析’,注意一定是先打開控制台,然后再去搜索,不然請求信息是沒有記錄下來的。
- 然后我們去
Network標簽下的XHR下查找我們需要的請求(動態加載的數請求都是在XHR下);

- 然后我們切換到
headers標簽下,我們可以看到請求的地址和所需到參數等信息;

- 實驗幾次之后我們便能發現這三個參數的含義分別是:
- first:是否首頁
- pn:頁碼
- kd:搜索關鍵詞
- 正常來說我們直接向這個網址傳
first,pn,kd三個參數就好了,不過嘗試了幾次之后發現拉勾有如下比較有意思的限制:- headers里面referer參數是必須的,referer是向服務器表示你是從哪個頁面跳轉過來的;
- 必須得先訪問這個referer的網址,然后再去請求職位信息的API。
- 代碼如下,也很簡單,不過三十幾行:
import requests
class Config:
kd = '數據分析'
referer = 'https://www.lagou.com/jobs/list_%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90?labelWords=&fromSearch=true&suginput='
headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Referer': referer,
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/'
'537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'}
class Spider:
def __init__(self, kd=Config.kd):
self.kd = kd
self.url = Config.referer
self.api = 'https://www.lagou.com/jobs/positionAjax.json'
# 必須先請求referer網址
self.sess = requests.session()
self.sess.get(self.url, headers=Config.headers)
def get_position(self, pn):
data = {'first': 'true',
'pn': str(pn),
'kd': self.kd
}
# 向API發起POST請求
r = self.sess.post(self.api, headers=Config.headers, data=data)
# 直接.json()解析數據
return r.json()['content']['positionResult']['result']
def engine(self, total_pn):
for pn in range(1, total_pn + 1):
results = self.get_position(pn)
for pos in results:
print(pos['positionName'], pos['companyShortName'], pos['workYear'], pos['salary'])
if __name__ == '__main__':
lagou = Spider()
lagou.engine(2)
- 附上執行結果:
數據分析-客服中心(J10558) 哈啰出行 3-5年 9k-15k
大數據分析師 建信金科 3-5年 16k-30k
......
數據分析師-【快影】 快手 3-5年 15k-30k
數據分析師(業務分析)-【商業化】 快手 3-5年 20k-40k
數據分析師 思創客 1-3年 6k-12k
全文完~~
