【爬蟲其實很簡單】模擬登陸與文件下載

準備階段:GET和POST是什么?

GET

還記得上一節的Bonus嗎?那里我們簡單介紹了GET請求的作用:向網站獲取資源,同時發送一定的數據(還記得王老五嗎?)。如果在GET中向網站發送數據,數據會被記錄在網址之中,通常都是以/?variable1=key1&variable2=key2&...的形式傳輸給服務器。我搭建了一個小網站來幫助大家理解GET請求,請點擊這里訪問。

你沒有向服務器傳輸數據

這里顯示了“你沒有向服務器傳輸任何數據”,因為我們還沒有在網址后面加?variable=key這樣的字段,所以網站只返回了基礎頁面。你現在可以試試在網址后面輸入?python=easy&learn=good然后訪問

返回了兩組數據

這里網站就接受到了兩組數據,第一組就是把變量名為python變量定義為easy,第二組就是把變量名learn變量定義為good。注意這里的python和learn變量都是在服務器中進行的處理。上次糗事百科的s也是服務器處理的變量。

s是服務器內部儲存的變量

如果你更改了變量名,網站可能就沒有辦法正確處理你的請求,網站訪問就可能出錯。

基于GET請求的這些特性,我們也可以把這樣的網址放在收藏夾里面。以后每次打開這個網站的時候都發送一樣的信息(比如每次都給糗事百科說:我是王老五)

POST

有的時候我們要登錄一個網站(比如知乎),或者填寫了一個表格,要發送給網站。所以瀏覽器要對網站說:你好,這里是我的賬號和密碼,麻煩讓我登陸一下可以嗎?或者說,你給我的表格我都填好了,你查收一下吧。

對于這種登錄、填表一類的數據,瀏覽器就會對網站發送POST請求,把表單信息(Form Data),也就是之前說的賬號密碼,問據調查一類的信息,發送到網站。

這一類的信息是不會儲存在網址里面的,所以你也不用擔心你的賬號密碼都明文發送到了服務器。

如何查看請求類型

我們打開開發者工具,點擊Network選單,任意點擊一個請求就可以看到它請求的類型。

GET請求
POST請求

今天的目標

我們的目標是什么?當然是沒有蛀牙

呸!

事情是這樣的,前幾個月老師派我們去參加了泰迪杯的教練員培訓,培訓是全程錄像的。老師希望我們能夠把錄像的視頻發給他。

視頻有這么多

大概看了一下,視頻有這么多!一個一個下載,那不是虐待自己嘛!

那怎么辦?要不用爬蟲來下載吧。

模擬登陸

由于泰迪杯網站問題,測試之后發現無法用正常的賬號密碼登陸,這里會使用訪客賬號登陸,其他網站分析步驟和此一致。

我們先打開泰迪杯的登陸界面,打開開發者工具,選擇Network選單,點擊訪客登陸。

操作流程

注意到index.php的資源請求是一個POST請求,我們把視窗拉倒最下面,看到表單數據(Form data),瀏覽器在表單數據中發送了兩個變量,分別是usernamepassword,兩個變量的值都是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里面。

右下角有Form Data

我們看到泰迪杯網站要求上傳的表單數據就是usernamepassword,兩者的值都是guest,所以在python里面我們創建一個dict,命名為data,里面的數據就輸入usernamepassword。最后再用s把數據post到網址,模擬登陸就完成了。

我們運行一下代碼,

登陸成功了

可以看到網址跳轉到了泰迪杯教程的首頁,和在瀏覽器里面的行為是一樣的。

恭喜你,你已經學會了如何模擬登陸一個網站。你可以給你自己鼓鼓掌??,然后我們開始進入下一個部分。

視頻下載

我們進入到我們要下載的視頻的頁面,然后對要下載的鏈接進行審查元素。

元素都在`a`標簽中

使用上篇教程中的分析方法我們不難發現,所有這樣的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,點擊這里訪問

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容