原文地址javascript
有時候咱們須要把一些經典的東西收藏起來,時時回味,而Coursera上的一些課程無疑就是經典之做。Coursera中的大部分完結課程都提供了完整的配套教學資源,包括ppt,視頻以及字幕等,離線下來後會很是便於學習。很明顯,咱們不會去一個文件一個文件的下載,只有傻子才那麼幹,程序員都是聰明人!html
那咱們聰明人準備怎麼辦呢?固然是寫一個腳原本批量下載了。首先咱們須要分析一下手工下載的流程:登陸本身的Coursera帳戶(有的課程須要咱們登陸並選課後才能看到相應的資源),在課程資源頁面裏,找到相應的文件連接,而後用喜歡的工具下載。java
很簡單是吧?咱們能夠用程序來模仿以上的步驟,這樣就能夠解放雙手了。整個程序分爲三個部分就能夠了:python
登陸Coursera;git
在課程資源頁面裏面找到資源連接;程序員
根據資源連接選擇合適的工具下載資源。github
下面就來具體的實現如下吧!web
剛開始時本身並無添加登陸模塊,覺得訪客就能夠下載相應的課程資源,後來在測試comnetworks-002
這門課程時發現訪客訪問資源頁面時會自動跳轉到登陸界面,下圖是chrome在隱身模式訪問該課程資源頁面時的狀況。正則表達式
要想模擬登陸,咱們先找到登陸的頁面,而後利用google的Developer Tools
分析帳號密碼是如何上傳到服務器的。chrome
咱們在登陸頁面的表單中填入帳號密碼,而後點擊登陸。與此同時,咱們須要雙眼緊盯Developer Tools——Network
,找到提交帳號信息的url。通常狀況下,若是要向服務器提交信息,通常都用post方法,這裏咱們只須要先找到Method爲post的url。悲劇的是,每次登陸帳號時,Network裏面都找不到提交帳戶信息的地址。猜想登陸成功後,直接跳轉到登陸成功後的頁面,想要找的內容一閃而過了。
因而就隨便輸入了一組帳號密碼,故意登陸失敗,果然找到了post的頁面地址,以下圖:
地址爲:https://accounts.coursera.org/api/v1/login
。爲了知道向服務器提交了哪些內容,進一步觀察post頁面中表單中內容,以下圖:
咱們看到一共有三個字段:
email:帳號的註冊郵箱
password:帳號密碼
webrequest:附加的字段,值爲true。
接下來就動手寫吧,我選擇用python的Requests
庫來模擬登陸,關於Requests官網是這樣介紹的。
Requests is an elegant and simple HTTP library for Python, built for human beings.
事實上requests用起來確實簡單方便,不虧是專門爲人類設計的http庫。requests提供了Session對象
,能夠用來在不一樣的請求中傳遞一些相同的數據,好比在每次請求中都攜帶cookie。
初步的代碼以下:
signin_url = "https://accounts.coursera.org/api/v1/login" logininfo = {"email": "...", "password": "...", "webrequest": "true" } user_agent = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/36.0.1985.143 Safari/537.36") post_headers = {"User-Agent": user_agent, "Referer": "https://accounts.coursera.org/signin" } coursera_session = requests.Session() login_res = coursera_session.post(signin_url, data=logininfo, headers=post_headers, ) if login_res.status_code == 200: print "Login Successfully!" else: print login_res.text
將表單中提交的內容存放在字典中,而後做爲data參數傳遞給Session.post函數。通常狀況下,最好是加上請求User-Agent
,Referer
等請求頭部,User-Agent用來模擬瀏覽器請求,Referer用來告訴服務器我是從referer頁面跳轉到請求頁面的,有時候服務器會檢查請求的Referer字段來保證是從固定地址跳到當前請求頁的。
上面片斷的運行結果很奇怪,顯示以下信息:Invalid CSRF Token
。後來在github上面搜索到一個Coursera的批量下載腳本,發現人家發送頁面請求時headers多了XCSRF2Cookie, XCSRF2Token, XCSRFToken, cookie
4個字段。因而又從新看了一下post頁面的請求頭部,發現確實有這幾個字段,估計是服務器端用來作一些限制的。
用瀏覽器登陸了幾回,發現XCSRF2Token, XCSRFToken是長度爲24的隨機字符串,XCSRF2Cookie爲"csrf2_token_"加上長度爲8的隨機字符串。不過一直沒搞明白Cookie是怎麼求出來的,不過看github上面代碼,Cookie彷佛只是"csrftoken"和其餘三個的組合,試了一下居然能夠。
在原來的代碼上添加如下部分就足夠了。
def randomString(length): return ''.join(random.choice(string.letters + string.digits) for i in xrange(length)) XCSRF2Cookie = 'csrf2_token_%s' % ''.join(randomString(8)) XCSRF2Token = ''.join(randomString(24)) XCSRFToken = ''.join(randomString(24)) cookie = "csrftoken=%s; %s=%s" % (XCSRFToken, XCSRF2Cookie, XCSRF2Token) post_headers = {"User-Agent": user_agent, "Referer": "https://accounts.coursera.org/signin", "X-Requested-With": "XMLHttpRequest", "X-CSRF2-Cookie": XCSRF2Cookie, "X-CSRF2-Token": XCSRF2Token, "X-CSRFToken": XCSRFToken, "Cookie": cookie }
至此登陸功能初步實現。
登陸成功後,咱們只須要get到資源頁面的內容,而後過濾出本身須要的資源連接就好了。資源頁面的地址很簡單,爲https://class.coursera.org/name/lecture
,其中name爲課程名稱。好比對於課程comnetworks-002,資源頁面地址爲https://class.coursera.org/comnetworks-002/lecture。
抓取到頁面資源後,咱們須要分析html文件,這裏選擇使用BeautifulSoup
。BeautifulSoup是一個能夠從HTML或XML文件中提取數據的Python庫,至關強大。具體使用官網上有很詳細的文檔,這裏再也不贅述。在使用BeautifulSoup前,咱們還得找出資源連接的規律,方便咱們過濾。
其中課程每週的總題目爲class=course-item-list-header
的div標籤下,每週的課程均在class=course-item-list-section-list
的ul標籤下,每節課程在一個li標籤中,課程資源則在li標籤中的div標籤中。
查看了幾門課程以後,發現過濾資源連接的方法很簡單,以下:
ppt和ppt資源:用正則表達式匹配連接;
字幕資源:找到title="Subtitles (srt)"
的標籤,取其href
屬性;
視頻資源:找到title="Video (MP4)"
的標籤,取其href
屬性便可。
字幕和視頻也能夠用正則表達式過濾,不過用BeautifulSoup根據title屬性來匹配,有更好的易讀性。而ppt和pdf資源,沒有固定的title屬性,只好利用正則表達式來匹配。
具體代碼以下:
soup = BeautifulSoup(content) chapter_list = soup.find_all("div", class_="course-item-list-header") lecture_resource_list = soup.find_all("ul", class_="course-item-list-section-list") ppt_pattern = re.compile(r'https://[^"]*\.ppt[x]?') pdf_pattern = re.compile(r'https://[^"]*\.pdf') for lecture_item, chapter_item in zip(lecture_resource_list, chapter_list): # weekly title chapter = chapter_item.h3.text.lstrip() for lecture in lecture_item: lecture_name = lecture.a.string.lstrip() # get resource link ppt_tag = lecture.find(href=ppt_pattern) pdf_tag = lecture.find(href=pdf_pattern) srt_tag = lecture.find(title="Subtitles (srt)") mp4_tag = lecture.find(title="Video (MP4)") print ppt_tag["href"], pdf_tag["href"] print srt_tag["href"], mp4_tag["href"]
既然已經獲得了資源連接,下載部分就很容易了,這裏我選擇使用curl來下載。具體思路很簡單,就是輸出curl resource_link -o file_name
到一個種子文件中去,好比到feed.sh中。這樣只須要給種子文件執行權限,而後運行種子文件便可。
爲了便於歸類課程資源,能夠爲課程每週的標題創建一個文件夾,以後該周的全部課程均下載在該目錄下。爲了方便咱們快速定位到每節課的全部資源,能夠把一節課的全部資源文件均命名爲課名.文件類型
。具體的實現比較簡單,這裏再也不給出具體程序了。能夠看一下一個測試例子中的feed.sh文件,部份內容以下:
mkdir 'Week 1: Introduction, Protocols, and Layering' cd 'Week 1: Introduction, Protocols, and Layering' curl https://d396qusza40orc.cloudfront.net/comnetworks/lect/1-readings.pdf -o '1-1 Goals and Motivation (15:46).pdf' curl https://class.coursera.org/comnetworks-002/lecture/subtitles?q=25_en&format=srt -o '1-1 Goals and Motivation (15:46).srt' curl https://class.coursera.org/comnetworks-002/lecture/download.mp4?lecture_id=25 -o '1-1 Goals and Motivation (15:46).mp4' curl https://d396qusza40orc.cloudfront.net/comnetworks/lect/1-readings.pdf -o '1-2 Uses of Networks (17:12).pdf' curl https://class.coursera.org/comnetworks-002/lecture/subtitles?q=11_en&format=srt -o '1-2 Uses of Networks (17:12).srt' curl https://class.coursera.org/comnetworks-002/lecture/download.mp4?lecture_id=11 -o '1-2 Uses of Networks (17:12).mp4'
到這裏爲止,咱們已經成功完成爬取Coursera課程資源的目標,具體的代碼放在gist上。使用時,咱們只須要運行程序,並把課程名稱做爲參數傳遞給程序就能夠了(這裏的課程名稱並非整個課程的完整名字,而是在課程介紹頁面地址中的縮略名字,好比Computer Networks這門課,課程名稱是comnetworks-002)。
其實,這個程序能夠看作一個簡單的小爬蟲程序了,下面粗略介紹下爬蟲的概念。
關於什麼是爬蟲,wiki上是這樣說的
A Web crawler is an Internet bot that systematically browses the World Wide Web, typically for the purpose of Web indexing.
爬蟲的整體架構圖以下(圖片來自wiki):
簡單來講,爬蟲從Scheduler中獲取初始的urls,下載相應的頁面,存儲有用的數據,同時分析該頁面中的連接,若是已經訪問就pass,沒訪問的話加入到Scheduler中等待抓取頁面。
固然有一些協議來約束爬蟲的行爲規範,好比許多網站都有一個robots.txt
文件來規定網站哪些內容能夠被爬取,哪些不能夠。
每一個搜索引擎背後都有一個強大的爬蟲程序,把觸角伸到網絡中的全部角落,不斷去收集有用信息,並創建索引。這種搜索引擎級別的爬蟲實現起來很是複雜,由於網絡上的頁面數量太過龐大,只是遍歷他們就已經很困難了,更不要說去分析頁面信息,並創建索引了。
實際應用中,咱們只須要爬取特定站點,抓取少許的資源,這樣實現起來簡單不少。不過仍然有許多讓人頭疼的問題,好比許多頁面元素是javascript生成的,這時候咱們須要一個javascript引擎,渲染出整個頁面,再加以過濾。
更糟糕的是,許多站點都會用一些措施來阻止爬蟲爬取資源,好比限定同一IP一段時間的訪問次數,或者是限制兩次操做的時間間隔,加入驗證碼等等。絕大多數狀況下,咱們不知道服務器端是如何防止爬蟲的,因此要想讓爬蟲工做起來確實挺難的。
參考:
github:coursera-dl/coursera
github:coursera-downloader
python爬取頁面元素失敗
Wiki: Web crawler
Python 爬蟲如何入門學習?