準備階段:GET和POST是什么?
GET
還記得上一節的Bonus嗎?那里我們簡單介紹了GET請求的作用:向網站獲取資源,同時發送一定的數據(還記得王老五嗎?)。如果在GET中向網站發送數據,數據會被記錄在網址之中,通常都是以/?variable1=key1&variable2=key2&...
的形式傳輸給服務器。我搭建了一個小網站來幫助大家理解GET請求,請點擊這里訪問。
這里顯示了“你沒有向服務器傳輸任何數據”,因為我們還沒有在網址后面加?variable=key
這樣的字段,所以網站只返回了基礎頁面。你現在可以試試在網址后面輸入?python=easy&learn=good
然后訪問。
這里網站就接受到了兩組數據,第一組就是把變量名為python變量定義為easy,第二組就是把變量名learn變量定義為good。注意這里的python和learn變量都是在服務器中進行的處理。上次糗事百科的s也是服務器處理的變量。
如果你更改了變量名,網站可能就沒有辦法正確處理你的請求,網站訪問就可能出錯。
基于GET請求的這些特性,我們也可以把這樣的網址放在收藏夾里面。以后每次打開這個網站的時候都發送一樣的信息(比如每次都給糗事百科說:我是王老五)
POST
有的時候我們要登錄一個網站(比如知乎),或者填寫了一個表格,要發送給網站。所以瀏覽器要對網站說:你好,這里是我的賬號和密碼,麻煩讓我登陸一下可以嗎?或者說,你給我的表格我都填好了,你查收一下吧。
對于這種登錄、填表一類的數據,瀏覽器就會對網站發送POST請求,把表單信息(Form Data),也就是之前說的賬號密碼,問據調查一類的信息,發送到網站。
這一類的信息是不會儲存在網址里面的,所以你也不用擔心你的賬號密碼都明文發送到了服務器。
如何查看請求類型
我們打開開發者工具,點擊Network選單,任意點擊一個請求就可以看到它請求的類型。
今天的目標
我們的目標是什么?當然是沒有蛀牙。
呸!
事情是這樣的,前幾個月老師派我們去參加了泰迪杯的教練員培訓,培訓是全程錄像的。老師希望我們能夠把錄像的視頻發給他。
大概看了一下,視頻有這么多!一個一個下載,那不是虐待自己嘛!
那怎么辦?要不用爬蟲來下載吧。
模擬登陸
由于泰迪杯網站問題,測試之后發現無法用正常的賬號密碼登陸,這里會使用訪客賬號登陸,其他網站分析步驟和此一致。
我們先打開泰迪杯的登陸界面,打開開發者工具,選擇Network選單,點擊訪客登陸。
注意到index.php
的資源請求是一個POST請求,我們把視窗拉倒最下面,看到表單數據(Form data),瀏覽器在表單數據中發送了兩個變量,分別是username
和password
,兩個變量的值都是guest
。這就是我們需要告訴網站的信息了。
知道了這些信息,我們就可以使用requesst來模擬登陸了。
import requests
s = requests.Session()
data = {
'username': 'guest',
'password': 'guest',
}
r = s.post('http://moodle.tipdm.com/login/index.php', data)
print(r.url)
同樣的,在第一行我們引入requests包。但與上次不同的是我們這次并沒有直接使用request.post()
,而是在第二行先創建了一個Session實例s
,Session實例可以將瀏覽過程中的cookies保存下來。
我們先來簡單認識一下cookies是什么:
cookies指網站為了辨別用戶身份而儲存在用戶本地終端(Client Side)上的數據(通常經過加密)
來源:維基百科
換句話說,泰迪杯的網站要聰明一點,不是只是用GET請求傳遞的數據來確認用戶身份,而是要用保存在本地的cookies來確認用戶身份(你再也不能偽裝成隔壁老王了)。
在python2.7大家通常使用urllib和urllib2包中,有一套很復雜的代碼來儲存cookies。但是在requests中,我們只要創建一個Session實例(比如這里的s
),然后之后的請求都用Session實例s
來發送,cookies的事情就不用管了。
我們再來看這幾行代碼:
data = {
'username': 'guest',
'password': 'guest',
}
r = s.post('http://moodle.tipdm.com/login/index.php', data)
s.post()
和上次教程中的requests.get()
是相對應的,一個發送POST請求,一個發送GET請求。上一篇中我們并沒有介紹Session實例,所以用的requests。在之后的請求發送中,大家盡量多使用s.get()
和s.post()
,這樣可以避免很多錯誤。
POST請求在之前講過了,是一定要向服務器發送一個表單數據(form data)的。那這個數據到底怎么發送,發送什么呢?答案就在開發者工具的Form Data里面。
我們看到泰迪杯網站要求上傳的表單數據就是username
和password
,兩者的值都是guest
,所以在python里面我們創建一個dict,命名為data,里面的數據就輸入username
和password
。最后再用s
把數據post到網址,模擬登陸就完成了。
我們運行一下代碼,
可以看到網址跳轉到了泰迪杯教程的首頁,和在瀏覽器里面的行為是一樣的。
恭喜你,你已經學會了如何模擬登陸一個網站。你可以給你自己鼓鼓掌??,然后我們開始進入下一個部分。
視頻下載
我們進入到我們要下載的視頻的頁面,然后對要下載的鏈接進行審查元素。
使用上篇教程中的分析方法我們不難發現,所有這樣的a
標簽(a tag)都在<div class="activityinstance">
標簽中。所以我們只要找到所有的class為acticityinstance的div
標簽,然后提取里面a
標簽的href
屬性,就知道視頻的地址了對吧?
同樣的,我們使用beautiful soup包來實現我們想要的功能。
from bs4 import BeautifulSoup
r = s.get('http://moodle.tipdm.com/course/view.php?id=16')
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all("div", class_='activityinstance')
for div in divs:
url = div.a.get('href')
print(url)
注意,現在所有的代碼看起來應該是這樣的:
import requests
from bs4 import BeautifulSoup
data = {
'username': 'guest',
'password': 'guest',
}
s = requests.Session()
r = s.post('http://moodle.tipdm.com/login/index.php', data)
r = s.get('http://moodle.tipdm.com/course/view.php?id=16')
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all("div", class_='activityinstance')
for div in divs:
url = div.a.get('href')
print(url)
運行一下,
恭喜,你現在離成功只有一步之遙了!
我們點開其中的一個網址,看看里面的結構:
可以看到下載鏈接已經在你面前了,我們對它進行審查元素,看到了一個.mp4的下載地址,那下一步我們就是要獲取這個mp4的下載地址。
我強烈建議你在這里暫停一下,先不要看下面的內容。試試自己寫能不能寫出來,寫不出來再看看下面給出來的代碼。
for div in divs[1:]: # 注意這里也出現了改動
url = div.a.get('href')
r = s.get(url)
soup = BeautifulSoup(r.text, 'lxml')
target_div = soup.find('div', class_='resourceworkaround')
target_url = target_div.a.get('href')
print(target_url)
divs[1:]
的意思是我們忽視掉divs
列表(list)中的第一個元素,然后進行下面的操作。
這里請你思考一個問題——為什么我們要忽視divs
中的第一個元素呢?
注意,到目前為止,你的代碼看起來應該是這樣的:
import requests
from bs4 import BeautifulSoup
data = {
'username': 'guest',
'password': 'guest',
}
s = requests.Session()
r = s.post('http://moodle.tipdm.com/login/index.php', data)
r = s.get('http://moodle.tipdm.com/course/view.php?id=16')
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all("div", class_='activityinstance')
for div in divs[1:]: # 注意這里也出現了改動
url = div.a.get('href')
r = s.get(url)
soup = BeautifulSoup(r.text, 'lxml')
target_div = soup.find('div', class_='resourceworkaround')
target_url = target_div.a.get('href')
print(target_url)
運行一下代碼:
恭喜你,你又成功了!你成功獲取到了視頻的下載地址。
現在將我在這里提供的代碼復制到你的代碼前面:
def download(url, s):
import urllib, os
file_name = urllib.parse.unquote(url)
file_name = file_name[file_name.rfind('/') + 1:]
try:
r = s.get(url, stream=True, timeout = 2)
chunk_size = 1000
timer = 0
length = int(r.headers['Content-Length'])
print('downloading {}'.format(file_name))
if os.path.isfile('./' + file_name):
print(' file already exist, skipped')
return
with open('./' + file_name, 'wb') as f:
for chunk in r.iter_content(chunk_size):
timer += chunk_size
percent = round(timer/length, 4) * 100
print('\r {:4f}'.format((percent)), end = '')
f.write(chunk)
print('\r finished ')
except requests.exceptions.ReadTimeout:
print('read time out, this file failed to download')
return
except requests.exceptions.ConnectionError:
print('ConnectionError, this file failed to download')
return
然后在你循環的末尾加上
download(target_url, s)
注意,現在整個代碼看起來是這樣的:
import requests
from bs4 import BeautifulSoup
data = {
'username': 'guest',
'password': 'guest',
}
def download(url, s):
import urllib, os
file_name = urllib.parse.unquote(url)
file_name = file_name[file_name.rfind('/') + 1:]
try:
r = s.get(url, stream=True, timeout = 2)
chunk_size = 1000
timer = 0
length = int(r.headers['Content-Length'])
print('downloading {}'.format(file_name))
if os.path.isfile('./' + file_name):
print(' file already exist, skipped')
return
with open('./' + file_name, 'wb') as f:
for chunk in r.iter_content(chunk_size):
timer += chunk_size
percent = round(timer/length, 4) * 100
print('\r {:4f}'.format((percent)), end = '')
f.write(chunk)
print('\r finished ')
except requests.exceptions.ReadTimeout:
print('read time out, this file failed to download')
return
except requests.exceptions.ConnectionError:
print('ConnectionError, this file failed to download')
return
s = requests.Session()
r = s.post('http://moodle.tipdm.com/login/index.php', data)
r = s.get('http://moodle.tipdm.com/course/view.php?id=16')
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all("div", class_='activityinstance')
for div in divs[1:]:
url = div.a.get('href')
r = s.get(url)
soup = BeautifulSoup(r.text, 'lxml')
target_div = soup.find('div', class_='resourceworkaround')
target_url = target_div.a.get('href')
download(target_url, s)
運行一下,
恭喜你,你已經成功學會了如何模擬登陸一個網站,并且學會了如何從網站上面下載一個文件。
你的進展非常的迅速,并且做的也非常好。你可以四處走動走動,休息一下。
如果你的精力還非常充沛,你可以試著分析一下我給出的download函數是怎么構成的。
爬蟲其實很簡單,對嗎?
Bonus
未完待續~
本篇教程代碼文件業已上傳Github,點擊這里訪問