了編寫一個Java爬蟲,你需要了解以下幾個步驟:
下面是一個基本的Java爬蟲代碼示例,它使用Jsoup解析器和URLConnection庫連接到目標網站并提取標題和鏈接信息:
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Scanner;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class SimpleWebCrawler {
public static void main(String[] args) {
String url = "https://www.example.com/";
try {
URLConnection conn = new URL(url).openConnection();
conn.addRequestProperty("User-Agent", "Mozilla/5.0");
Scanner scanner = new Scanner(conn.getInputStream());
String html = scanner.useDelimiter("\\Z").next();
scanner.close();
Document doc = Jsoup.parse(html);
Elements links = doc.select("a[href]");
for (Element link : links) {
System.out.println(link.attr("href") + " - " + link.text());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Jsoup是一款用于解析HTML和XML文檔的Java庫。它提供了類似于jQuery的語法來操作文檔,使得解析和處理文檔變得非常簡單。
以下是Jsoup解析器的一些常用功能:
總之,Jsoup是一款非常實用的HTML和XML解析器,可以幫助Java開發者快速、簡單地解析和處理HTML文檔,使得爬蟲開發變得更加容易。
使用Jsoup解析器需要先將其添加到項目的依賴中。可以通過Maven或者Gradle來添加依賴。
例如,使用Maven添加Jsoup的依賴:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
添加依賴之后,就可以在Java代碼中使用Jsoup了。以下是使用Jsoup解析器獲取HTML文檔中所有鏈接的示例代碼:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class JsoupExample {
public static void main(String[] args) {
String html = "<html><head><title>Jsoup Example</title></head>"
+ "<body><p>Jsoup is a Java library for working with real-world HTML.</p>"
+ "<a href=\"http://example.com\">Example</a></body></html>";
Document doc = Jsoup.parse(html); // 將HTML字符串解析為文檔對象
Elements links = doc.select("a"); // 獲取所有的鏈接元素
for (Element link : links) {
String href = link.attr("href"); // 獲取鏈接的URL地址
String text = link.text(); // 獲取鏈接的文本內容
System.out.println(href + ": " + text);
}
}
}
以上代碼使用Jsoup將HTML字符串解析為文檔對象,然后使用選擇器語法獲取所有的鏈接元素,并輸出它們的URL地址和文本內容。
除此之外,Jsoup還有很多其他的功能,例如修改元素、過濾HTML文檔等等,可以根據具體需求靈活運用。
1.獲取網頁的 Title:
Document doc = Jsoup.connect("http://example.com/").get();
String title = doc.title();
2.獲取指定標簽的文本內容:
Element element = doc.select("div.content").first();
String text = element.text();
3.獲取指定屬性的值:
Element element = doc.select("img").first();
String src = element.attr("src");
4.過濾 HTML 標簽:
String html = "<p>這是一段 <b>加粗</b> 的文本。</p>";
String text = Jsoup.parse(html).text();
5.修改 HTML 內容:
Element element = doc.select("div.content").first();
element.append("<p>這是新增的文本內容。</p>");
6.提取網頁中的鏈接:
Elements links = doc.select("a[href]");
for (Element link : links) {
String href = link.attr("href");
System.out.println(href);
}
7.提取網頁中的圖片:
Elements imgs = doc.select("img[src~=(?i)\\.(png|jpe?g|gif)]");
for (Element img : imgs) {
String src = img.attr("src");
System.out.println(src);
}
這些只是 Jsoup 解析器的常見用法之一。Jsoup 還有更多的功能,如解析 XML、處理表單、處理 Cookie 等,大家可以自己去了解!
有不足之處大家也可以在評論區指出!
言
在當前數據爆發的時代,數據分析行業勢頭強勁,越來越多的人涉足數據分析領域。進入領域最想要的就是獲取大量的數據來為自己的分析提供支持,但是如何獲取互聯網中的有效信息?這就促進了“爬蟲”技術的飛速發展。
網絡爬蟲(又被稱為網頁蜘蛛,網絡機器人,在FOAF社區中間,更經常的稱為網頁追逐者),是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本。
傳統爬蟲從一個或若干初始網頁的URL開始,獲得初始網頁上的URL,在抓取網頁的過程中,不斷從當前頁面上抽取新的URL放入隊列,直到滿足系統的一定停止條件。聚焦爬蟲的工作流程較為復雜,需要根據一定的網頁分析算法過濾與主題無關的鏈接,保留有用的鏈接并將其放入等待抓取的URL隊列。然后,它將根據一定的搜索策略從隊列中選擇下一步要抓取的網頁URL,并重復上述過程,直到達到系統的某一條件時停止。
另外,所有被爬蟲抓取的網頁將會被系統存貯,進行一定的分析、過濾,并建立索引,以便之后的查詢和檢索;對于聚焦爬蟲來說,這一過程所得到的分析結果還可能對以后的抓取過程給出反饋和指導。
筆者是爬蟲初學者,通過這篇綜述來記錄一下自己的心得體會。
以下為文章主要內容:
1. 初見爬蟲
使用Python中的Requests第三方庫。在Requests的7個主要方法中,最常使用的就是get()方法,通過該方法構造一個向服務器請求資源的Request對象,結果返回一個包含服務器資源的額Response對象。通過Response對象則可以獲取請求的返回狀態、HTTP響應的字符串即URL對應的頁面內容、頁面的編碼方式以及頁面內容的二進制形式。
在了解get()方法之前我們先了解一下HTTP協議,通過對HTTP協議來理解我們訪問網頁這個過程到底都進行了哪些工作。
1.1 淺析HTTP協議
超文本傳輸協議(HTTP,HyperText Transfer Protocol)是互聯網上應用最為廣泛的一種網絡協議。所有的www文件都必須遵守這個標準。HTTP協議主要有幾個特點:
支持客戶/服務器模式
簡單快捷:客服向服務器發出請求,只需要傳送請求方法和路徑。請求方法常用的有GET, HEAD, POST。每種方法規定了客戶與服務器聯系的類型不同。由于HTTP協議簡單,使得HTTP服務器的程序規模小,因而通信速度快。
靈活:HTTP允許傳輸任意類型的數據對象。
無連接:無連接的含義是限制每次連接請求只處理一個請求。服務器處理完客戶的請求,收到客戶的應答后即斷開連接,這種方式可以節省傳輸時間。
無狀態:HTTP協議是無狀態協議。無狀態是指協議對于事物處理沒有記憶能力。缺少狀態意味著如果后續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大,另一方面,在服務器不需要先前信息時它的應答就較快。
下面通過一張圖我們來了解一下訪問網頁的過程都發生了什么:
1. 首先瀏覽器拿到網址之后先將主機名解析出來。如 http://www.baidu.com/index.html 則會將主機名www.baidu.com 解析出來。
2. 查找ip,根據主機名,會首先查找ip,首先查詢hosts文件,成功則返回對應的ip地址,如果沒有查詢到,則去DNS服務器查詢,成功就返回ip,否則會報告連接錯誤。
3. 發送http請求,瀏覽器會把自身相關信息與請求相關信息封裝成HTTP請求 消息發送給服務器。
4. 服務器處理請求,服務器讀取HTTP請求中的內容,在經過解析主機,解析站點名稱,解析訪問資源后,會查找相關資源,如果查找成功,則返回狀態碼200,失敗就會返回大名鼎鼎的404了,在服務器監測到請求不在的資源后,可以按照程序員設置的跳轉到別的頁面。所以有各種有個性的404錯誤頁面。
5. 服務器返回HTTP響應,瀏覽器得到返回數據后就可以提取數據,然后調用解析內核進行翻譯,最后顯示出頁面。之后瀏覽器會對其引用的文件比如圖片,css,js等文件不斷進行上述過程,直到所有文件都被下載下來之后,網頁就會顯示出來。
HTTP請求,http請求由三部分組成,分別是:請求行、消息報頭、請求正文。請求方法(所有方法全為大寫)有多種,各個方法的解釋如下:
GET 請求獲取Request-URI所標識的資源
POST 在Request-URI所標識的資源后附加新的數據
HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭
PUT 請求服務器存儲一個資源,并用Request-URI作為其標識
DELETE 請求服務器刪除Request-URI所標識的資源
TRACE 請求服務器回送收到的請求信息,主要用于測試或診斷
CONNECT 保留將來使用
OPTIONS 請求查詢服務器的性能,或者查詢與資源相關的選項和需求
GET方法應用舉例:在瀏覽器的地址欄中輸入網址的方式訪問網頁時,瀏覽器采用GET方法向服務器獲取資源,eg:GET /form.html HTTP/1.1 (CRLF)
HTTP響應也是由三個部分組成,分別是:狀態行、消息報頭、響應正文。
狀態行格式如下:HTTP-Version Status-Code Reason-Phrase CRLF,其中,HTTP-Version表示服務器HTTP協議的版本;Status-Code表示服務器發回的響應狀態代碼;Reason-Phrase表示狀態代碼的文本描述。
狀態代碼有三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:
1xx:指示信息--表示請求已接收,繼續處理
2xx:成功--表示請求已被成功接收、理解、接受
3xx:重定向--要完成請求必須進行更進一步的操作
4xx:客戶端錯誤--請求有語法錯誤或請求無法實現
5xx:服務器端錯誤--服務器未能實現合法的請求
常見狀態代碼、狀態描述、說明:
200 OK //客戶端請求成功
400 Bad Request //客戶端請求有語法錯誤,不能被服務器所理解
401 Unauthorized //請求未經授權,這個狀態代碼必須和WWW-Authenticate報頭域一起使用
403 Forbidden //服務器收到請求,但是拒絕提供服務
404 Not Found //請求資源不存在,eg:輸入了錯誤的URL
500 Internal Server Error //服務器發生不可預期的錯誤
503 Server Unavailable //服務器當前不能處理客戶端的請求,一段時間后可能恢復正常。
eg:HTTP/1.1 200 OK (CRLF)
詳細的HTTP協議可以參考這篇文章:
http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html
前面我們了解了HTTP協議,那么我們訪問網頁的過程,那么網頁在是什么樣子的。爬蟲眼中的網頁又是什么樣子的。
網是靜態的,但爬蟲是動態的,所以爬蟲的基本思想就是沿著網頁(蜘蛛網的節點)上的鏈接的爬取有效信息。當然網頁也有動態(一般用PHP或ASP等寫成,例如用戶登陸界面就是動態網頁)的,但如果一張蛛網搖搖欲墜,蜘蛛會感到不那么安穩,所以動態網頁的優先級一般會被搜索引擎排在靜態網頁的后面。
知道了爬蟲的基本思想,那么具體如何操作呢?這得從網頁的基本概念說起。一個網頁有三大構成要素,分別是html文件、css文件和JavaScript文件。如果把一個網頁看做一棟房子,那么html相當于房子外殼;css相當于地磚涂料,美化房子外觀內飾;JavaScript則相當于家具電器浴池等,增加房子的功能。從上述比喻可以看出,html才是網頁的根本,畢竟地磚顏料在市場上也有,家具電器都可以露天擺設,而房子外殼才是獨一無二的。
下面就是一個簡單網頁的例子:
而在爬蟲眼里,這個網頁是這樣的:
因此網頁實質上就是超文本(hypertext),網頁上的所有內容都是在形如“<>...</>”這樣的標簽之內的。如果我們要搜集網頁上的所有超鏈接,只需尋找所有標簽中前面是"href="的字符串,并查看提取出來的字符串是否以"http"(超文本轉換協議,https表示安全的http協議)開頭即可。如果超鏈接不以"http"開頭,那么該鏈接很可能是網頁所在的本地文件或者ftp或smtp(文件或郵件轉換協議),應該過濾掉。
在Python中我們使用Requests庫中的方法來幫助我們實現對網頁的請求,從而達到實現爬蟲的過程。
1.2 Requests庫的7個主要方法:
最常用的方法get用來實現一個簡單的小爬蟲,通過示例代碼展示:
2. Robots協議
Robots協議(也稱為爬蟲協議、機器人協議等)的全稱是“網絡爬蟲排除標準”(Robots Exclusion Protocol),網站通過Robots協議告訴搜索引擎哪些頁面可以抓取,哪些頁面不能抓取。通過幾個小例子來解讀一下robots.txt中的內容,robots.txt默認放置于網站的根目錄小,對于一個沒有robots.txt文件的網站,默認是允許所有爬蟲獲取其網站內容的。
我們對于robots協議的理解,如果是商業利益我們是必須要遵守robots協議內容,否則會承擔相應的法律責任。當只是個人玩轉網頁、練習則是建議遵守,提高自己編寫爬蟲的友好程度。
3. 網頁解析
BeautifulSoup嘗試化平淡為神奇,通過定位HTML標簽來格式化和組織復雜的網絡信息,用簡單易用的Python對象為我們展示XML結構信息。
BeautifulSoup是解析、遍歷、維護“標簽樹”的功能庫。
3.1 BeautifulSoup的解析器
BeautifulSoup通過以上四種解析器來對我們獲取的網頁內容進行解析。使用官網的例子來看一下解析結果:
首先獲取以上的一段HTML內容,我們通過BeautifulSoup解析之后,并且輸出解析后的結果來對比一下:
通過解析的網頁內容,我們就可以使用BeautifulSoup中的方法來輕而易舉的獲得網頁中的主要信息:
3.2 BeautifulSoup類的基本元素
3.3 BeautifulSoup的遍歷功能
遍歷分為上行遍歷、下行遍歷、平行遍歷三種。
下行遍歷:
上行遍歷:
平行遍歷:
4. 正則表達式
正則表達式,又稱規則表達式。(英語:Regular Expression,在代碼中常簡寫為regex、regexp或RE),計算機科學的一個概念。正則表通常被用來檢索、替換那些符合某個模式(規則)的文本。
筆者也是初學正則表達式,感覺自己不能簡潔清晰的講述正則表達式,建議參考網上的教程( http://deerchao.net/tutorials/regex/regex.htm#mission )圖文并茂,詳細講解了正則表達式。
通過掌握正則表示也可以幫助我們獲取網頁中的主要信息。
5. 爬蟲框架Scrapy
Scrapy是Python開發的一個快速,高層次的屏幕抓取和web抓取框架,用于抓取web站點并從頁面中提取結構化的數據。Scrapy用途廣泛,可以用于數據挖掘、監測和自動化測試。
Scrapy吸引人的地方在于它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如BaseSpider、sitemap爬蟲等,最新版本又提供了web2.0爬蟲的支持。
5.1 Scrapy爬蟲框架結構
Engine: 控制所有模塊之間的數據流、根據條件觸發事件。
Downloader: 根據請求下載網頁
Scheduler: 對所有爬去請求進行調度管理
Spider: 解析Downloader返回的響應、產生爬取項、產生額外的爬去請求。
Item Pipelines: 以流水線方式處理Spider產生的爬取項、可能包括清理、檢驗和查重爬取項中的HTML數據、將數據存儲到數據庫。
5.2 數據流
1. 引擎打開一個網站(open a domain),找到處理該網站的Spider并向該spider請求第一個要爬取的URL(s)。
2. 引擎從Spider中獲取到第一個要爬取的URL并在調度器(Scheduler)以Request調度。
3. 引擎向調度器請求下一個要爬取的URL。
4. 調度器返回下一個要爬取的URL給引擎,引擎將URL通過下載中間件(請求(request)方向)轉發給下載器(Downloader)。
5. 一旦頁面下載完畢,下載器生成一個該頁面的Response,并將其通過下載中間件(返回(response)方向)發送給引擎。
6. 引擎從下載器中接收到Response并通過Spider中間件(輸入方向)發送給Spider處理。
7. Spider處理Response并返回爬取到的Item及(跟進的)新的Request給引擎。
8. 引擎將(Spider返回的)爬取到的Item給Item Pipeline,將(Spider返回的)Request給調度器。
9. (從第二步)重復直到調度器中沒有更多地request,引擎關閉該網站。
6. 分布式爬蟲
6.1 多線程爬蟲
在爬取數據量小的情況下,我們使用的都是串行下載網頁的,只有前一次下載完成之后才會啟動新的下載。數據量小的情況下尚可應對。但面對大型網站就會顯得性能不足,如果我們可以同時下載多個網頁,那么下載時間將會得到顯著改善。
我們將串行下載爬蟲擴展成并行下載。需要注意的是如果濫用這一功能,多線程爬蟲請求內容過快,可能會造成服務器過載,或是IP地址被封禁。為了避免這一問題,我們的爬蟲就要設置一個delay標識,用于設定請求同一域名時的最小時間間隔。
在Python中實現多線程是比較簡單的,Python中的thread模塊是比較底層的模塊,Python的threading模塊是對thread做了一些封裝,可以更加方便的被使用。
簡要的看一下thread模塊中含函數和常量:
Thread中常用的函數和對象:
一般來說,使用線程有兩種模式, 一種是創建線程要執行的函數, 把這個函數傳遞進Thread對象里,讓它來執行. 另一種是直接從Thread繼承,創建一個新的class,把線程執行的代碼放到這個新的class里。
實現多進程的代碼和例子參考:
http://www.jianshu.com/p/86b8e78c418a
6.2 多進程爬蟲
Python中的多線程其實并不是真正的多線程,并不能做到充分利用多核CPU資源。
如果想要充分利用,在python中大部分情況需要使用多進程,那么這個包就叫做 multiprocessing。
借助它,可以輕松完成從單進程到并發執行的轉換。multiprocessing支持子進程、通信和共享數據、執行不同形式的同步,提供了Process、Queue、Pipe、Lock等組件。
Process基本使用:
在multiprocessing中,每一個進程都用一個Process類來表示。首先看下它的API:
target 表示調用對象,你可以傳入方法的名字
args 表示被調用對象的位置參數元組,比如target是函數a,他有兩個參數m,n,那么args就傳入(m, n)即可
kwargs 表示調用對象的字典
name 是別名,相當于給這個進程取一個名字
group 分組,實際上不使用
我們先用一個實例來感受一下:
最簡單的創建Process的過程如上所示,target傳入函數名,args是函數的參數,是元組的形式,如果只有一個參數,那就是長度為1的元組。
然后調用start()方法即可啟動多個進程了。
另外你還可以通過 cpu_count() 方法還有 active_children() 方法獲取當前機器的 CPU 核心數量以及得到目前所有的運行的進程。
通過一個實例來感受一下:
運行結果:
通過開啟多個進程實現爬蟲,會大大縮減爬取信息的速度。詳細介紹請參考:
http://cuiqingcai.com/3335.html
7. 異步網站數據采集
在收集網頁信息時我們會遇到,網頁的加載模型為瀑布流形式,頁面URL沒有改變,但依然可以加載出內容。這時候就需要我們分析網頁中JavaScript中的一些代碼,從中獲取我們所需要的數據。
面對使用JS渲染的頁面推薦使用PhantomJS,無界面,可腳本編程的WebKit瀏覽器。參考 : http://cuiqingcai.com/2577.html
Selenium一種自動化測試工具。可以方便實現Web界面測試。使用PhantomJS渲染解析JS,Selenium用來驅動以及寫與Python的對接,然后Python進行后期處理。參考: http://cuiqingcai.com/2599.html
8. 爬蟲的存儲
在剛開始接觸爬蟲的時候,我們習慣將小的爬蟲結果輸出在命令行中,看著命令行中一行行的數據顯得頗有成就感,但是隨著數據的增多,并且需要進行數據分析時,將數據打印到命令行就不是辦法了。為了可以遠程使用大部分網絡爬蟲,我們還是需要將收集的數據存儲起來。
8.1 媒體文件
媒體文件常見的有兩種存儲方式:只獲取URL鏈接,或者直接把源文件下載下來。但是推薦使用第一種方式。優點如下:
爬蟲運行的更快,耗費的流量更少,因為只存儲鏈接,不需要下載文件。
節省存儲空間,因為不需要存儲媒體文件。
存儲URL的代碼更容易寫,也不需要實現文件下載代碼
不下載文件能夠降低目標主機服務器的負載。
當然這樣做也存在一些缺點:
內嵌在我們網頁中的外站鏈接被稱為盜鏈,使用這種鏈接會讓我們麻煩不斷,每個網站都會實施防盜鏈措施。
因為你的鏈接文件在別人的服務器,所以我們的應用就要跟著別人的節奏運行了。
盜鏈很容易改變,如果把盜鏈放在博客等地,被對方發現很可能被惡搞。或者是把URL存儲備用,等到用的時候發現鏈接已經過期了。
在現實中網絡瀏覽器不僅可以訪問HTML頁面并切換頁面,它們也會下載訪問頁面上的所有資源。下載文件會讓我們的爬蟲看起來更像人在瀏覽頁面。
8.2 把數據存儲到CSV
CSV是存儲表格數據的常用文件格式。每行都用一個換行符分隔,列與列之間用逗號分隔。Python中的CSV庫可以非常簡單的修改CSV文件,也可以從零開始創建一個CSV文件:
我們可以使用csv模塊提供的功能將爬蟲獲取的信息存入csv文件中。
8.3 MySQL
對于大量的爬蟲數據,并且在之后我們需要反復用來篩選分析的數據,我們選擇存儲在數據庫中。
MySQL是目前最受歡迎的開源關系型數據庫管理系統,它是一種非常靈活、穩定、功能齊全的DBMS,許多頂級網站都在用它,YouTube、Twitter、Facebook等。
Python中沒有內置的MySQL支持工具,不過,有很多開源的庫可以用來與MySQL做交互,最為出名的就是PyMySQL。
結合上述過程將爬蟲獲取到的數據存入數據庫中。
9. 爬蟲的常見技巧
9.1 模擬登錄
目前的網站多是采用cookie跟蹤用戶是否已經登錄的信息。一旦網站驗證了你的登錄權證,它就會保存在你瀏覽器的cookie中,里面通常包含一個服務器生成的命令牌、登錄有效時限和狀態跟蹤信息。網站會把這個cookie當作信息驗證的證據,在我們瀏覽網站的每個頁面時出示給服務器。
通過Chrome等瀏覽器自帶的開發者工具,我們從Network中獲取請求網頁的頭部和表單,在Header中我們就可以查看cookie中存儲的登錄信息,我們可以通過Scrapy設置請求網頁的頭部信息,并將cookie存儲在本地,來實現模擬登陸的效果。詳細的操作可以查看博客:http://www.jianshu.com /p/b7f41df6202d
9.2 網頁驗證碼
簡單的說,驗證碼就是一張圖片,圖片上有字符串。網站是如何實現的呢?有WEB基礎的人可能會知道,每個瀏覽器基本都有cookie,作為這次回話的唯一標示。每次訪問網站,瀏覽器都會把這個cookie發送給服務器。驗證碼就是和這個cookie綁定到一起的。如何理解呢?舉個例子,現在有網站W,有A和B兩個人,同時訪問W,W給A返回的驗證碼是X,給B返回的驗證碼是Y,這兩個驗證碼都是正確的,但是如果A輸入了B的驗證碼,肯定驗證不通過。那服務器是怎么區分A和B呢,就是用到的cookie。再舉個例子,有些網站你登錄一次之后,下次繼續訪問可能就自動登陸了,也是用cookie來標示唯一身份的,如果清除了cookie也就無法自動登陸了。
對于一些簡單的驗證碼我們可以通過機器識別,但是對于一些人眼都很難識別的驗證碼就只能尋找更加復雜的技術了。簡單的驗證碼識別過程就是對驗證碼圖片的一個處理過程。
灰度圖轉換,可以結合opencv中的imread方法。
圖像去噪(均值濾波器、高斯濾波器等等)。
圖像二值化(這個過程中驗證碼中的字符串已經成為黑色的,底色為白色)。
使用圖像識別方式,識別圖中的字符串達到識別驗證碼的目的。
推薦閱讀:
http://www.jianshu.com/p/dd699561671b
http://www.cnblogs.com/hearzeus/p/5166299.html(上篇)
http://www.cnblogs.com/hearzeus/p/5226546.html(下篇)
9.3 爬蟲代理池
由于筆者是個爬蟲初學者也沒有用到過這么復雜的技術,不過筆者在爬蟲的過程中的確是體會了被封IP地址的痛苦。所以推薦大家有精力的可以來學習并完成一個。
推薦閱讀:
https://www.zhihu.com/question/47464143
10. 防爬蟲
由于暴力爬蟲會對網站的服務器產生很大的壓力,所以各個網站對爬蟲都有限制,大多數網站會定義robots.txt.文件可以讓爬蟲了解該網站的限制。限制是作為建議給出。但是爬蟲前檢查該文件可以最小化我們的爬蟲被封禁的可能。
一篇關于反爬蟲的文章: https://segmentfault.com/a/ 1190000005840672 (來自攜程技術中心)
11. 學習資料
推薦書籍:
《Python網絡數據采集》 陶俊杰、陳小莉 譯
《用Python寫網絡爬蟲》 李斌 譯
推薦博客:
崔慶才得個人博客,有大量關于爬蟲的文章,而且講解的比較細致。
http://cuiqingcai.com/
數據挖掘與入門實戰微信公眾號分享的一篇文章,《Python開源爬蟲項目代碼:抓取淘寶、京東、QQ、知網數據》,有十九個開源的爬蟲項目,可以給大家提供參考。https://github.com/hlpassion/blog/issues/6
推薦視頻:
網易云課堂,例子清晰,可以跟做。
http://study.163.com/course/introduction.htm?courseId=1002794001#/courseDetail
Python網絡爬蟲與信息提取
http://www.icourse163.org/course/BIT-1001870001
- 更多精彩請關注清華-青島數據科學研究院微信官方公眾平臺“THU數據派”
強烈建議:請在電腦的陪同下,閱讀本文。本文以實戰為主,閱讀過程如稍有不適,還望多加練習。
本文的實戰內容有:
網絡爬蟲,也叫網絡蜘蛛(Web Spider)。它根據網頁地址(URL)爬取網頁內容,而網頁地址(URL)就是我們在瀏覽器中輸入的網站鏈接。比如:https://www.baidu.com/,它就是一個URL。
在講解爬蟲內容之前,我們需要先學習一項寫爬蟲的必備技能:審查元素(如果已掌握,可跳過此部分內容)。
在瀏覽器的地址欄輸入URL地址,在網頁處右鍵單擊,找到檢查,如下圖所示:(不同瀏覽器的叫法不同,Chrome瀏覽器叫做檢查,Firefox瀏覽器叫做查看元素,但是功能都是相同的)
我們可以看到,右側出現了一大推代碼,這些代碼就叫做HTML。什么是HTML?舉個容易理解的例子:我們的基因決定了我們的原始容貌,服務器返回的HTML決定了網站的原始容貌。
為啥說是原始容貌呢?因為人可以整容啊!扎心了,有木有?那網站也可以"整容"嗎?可以!請看下圖:
我能有這么多錢嗎?顯然不可能。我是怎么給網站"整容"的呢?就是通過修改服務器返回的HTML信息。我們每個人都是"整容大師",可以修改頁面信息。我們在頁面的哪個位置點擊審查元素,瀏覽器就會為我們定位到相應的HTML位置,進而就可以在本地更改HTML信息。
再舉個小例子:我們都知道,使用瀏覽器"記住密碼"的功能,密碼會變成一堆小黑點,是不可見的。可以讓密碼顯示出來嗎?可以,只需給頁面"動個小手術"!以淘寶為例,在輸入密碼框處右鍵,點擊檢查。
可以看到,瀏覽器為我們自動定位到了相應的HTML位置。將下圖中的password屬性值改為text屬性值(直接在右側代碼處修改):
就這樣,瀏覽器"記住的密碼"顯現出來了:
說這么多,什么意思呢?瀏覽器就是作為客戶端從服務器端獲取信息,然后將信息解析,并展示給我們的。我們可以在本地修改HTML信息,為網頁"整容",但是我們修改的信息不會回傳到服務器,服務器存儲的HTML信息不會改變。刷新一下界面,頁面還會回到原本的樣子。這就跟人整容一樣,我們能改變一些表面的東西,但是不能改變我們的基因。
網絡爬蟲的第一步就是根據URL,獲取網頁的HTML信息。在Python3中,可以使用urllib.request和requests進行網頁爬取。
requests庫強大好用,所以本文使用requests庫獲取網頁的HTML信息。requests庫的github地址:https://github.com/requests/requests
(1)requests安裝
在學習使用requests庫之前,我們需要在電腦中安裝好requests庫。在cmd中,使用如下指令安裝requests庫:
使用pip和easy_install都可以安裝,二選一即可。
(2)簡單實例
安裝好requests庫之后,我們先來大體瀏覽一下requests庫的基礎方法:
官方中文教程地址:
http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
requests庫的開發者為我們提供了詳細的中文教程,查詢起來很方便。本文不會對其所有內容進行講解,摘取其部分使用到的內容,進行實戰說明。
首先,讓我們看下requests.get()方法,它用于向服務器發起GET請求,不了解GET請求沒有關系。我們可以這樣理解:get的中文意思是得到、抓住,那這個requests.get()方法就是從服務器得到、抓住數據,也就是獲取數據。讓我們看一個例子(以 www.gitbook.cn為例)來加深理解:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://gitbook.cn/'
req = requests.get(url=target)
print(req.text)
requests.get()方法必須設置的一個參數就是url,因為我們得告訴GET請求,我們的目標是誰,我們要獲取誰的信息。我們將GET請求獲得的響應內容存放到req變量中,然后使用req.text就可以獲得HTML信息了。運行結果如下:
左側是我們程序獲得的結果,右側是我們在www.gitbook.cn網站審查元素獲得的信息。我們可以看到,我們已經順利獲得了該網頁的HTML信息。這就是一個最簡單的爬蟲實例,可能你會問,我只是爬取了這個網頁的HTML信息,有什么用呢?客官稍安勿躁,接下來進入我們的實戰正文。
實戰內容由簡單到復雜,難度逐漸增加,但均屬于入門級難度。下面開始我們的第一個實戰內容:網絡小說下載。
(1)實戰背景
小說網站《筆趣看》URL:http://www.biqukan.com/
《筆趣看》是一個盜版小說網站,這里有很多起點中文網的小說,該網站小說的更新速度稍滯后于起點中文網正版小說的更新速度。并且該網站只支持在線瀏覽,不支持小說打包下載。因此,本次實戰就是從該網站爬取并保存一本名為《一念永恒》的小說,該小說是耳根正在連載中的一部玄幻小說。PS:本實例僅為交流學習,支持耳根大大,請上起點中文網訂閱。
(2)小試牛刀
我們先看下《一念永恒》小說的第一章內容,URL:http://www.biqukan.com/1_1094/5403177.html
用已經學到的知識獲取HTML信息試一試,編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url=target)
print(req.text)
運行代碼,可以看到如下結果:
可以看到,我們很輕松地獲取了HTML信息。但是,很顯然,很多信息是我們不想看到的,我們只想獲得如右側所示的正文內容,我們不關心那些看著眼暈的英文字母。如何把正文內容從這些眾多的HTML信息中提取出來呢?這就是本小節實戰的主要內容。
(3)Beautiful Soup
爬蟲的第一步,獲取整個網頁的HTML信息,我們已經完成。接下來就是爬蟲的第二步,解析HTML信息,提取我們感興趣的內容。對于本小節的實戰,我們感興趣的內容就是文章的正文。提取的方法有很多,例如使用正則表達式、Xpath、Beautiful Soup等。對于初學者而言,最容易理解,并且使用簡單的方法就是使用Beautiful Soup提取感興趣內容。
Beautiful Soup的安裝方法和requests一樣,使用如下指令安裝(也是二選一):
一個強大的第三方庫,都會有一個詳細的官方文檔。我們很幸運,Beautiful Soup也是有中文的官方文檔。URL:
http://beautifulsoup.readthedocs.io/zh_CN/latest/
同理,我會根據實戰需求,講解Beautiful Soup庫的部分使用方法,更詳細的內容,請查看官方文檔。
現在,我們使用已經掌握的審查元素方法,查看一下我們的目標頁面,你會看到如下內容:
不難發現,文章的所有內容都放在了一個名為div的“東西下面”,這個"東西"就是html標簽。HTML標簽是HTML語言中最基本的單位,HTML標簽是HTML最重要的組成部分。不理解,沒關系,我們再舉個簡單的例子:
html標簽就像一個個“口袋”,每個“口袋”都有自己的特定功能,負責存放不同的內容。顯然,上述例子中的div標簽下存放了我們關心的正文內容。這個div標簽是這樣的:
<div id="content", class="showtxt">
細心的朋友可能已經發現,除了div字樣外,還有id和class。id和class就是div標簽的屬性,content和showtxt是屬性值,一個屬性對應一個屬性值。這東西有什么用?它是用來區分不同的div標簽的,因為div標簽可以有很多,我們怎么加以區分不同的div標簽呢?就是通過不同的屬性值。
仔細觀察目標網站一番,我們會發現這樣一個事實:class屬性為showtxt的div標簽,獨一份!這個標簽里面存放的內容,是我們關心的正文部分。
知道這個信息,我們就可以使用Beautiful Soup提取我們想要的內容了,編寫代碼如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url = target)
html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt')
print(texts)
在解析html之前,我們需要創建一個Beautiful Soup對象。BeautifulSoup函數里的參數就是我們已經獲得的html信息。然后我們使用find_all方法,獲得html信息中所有class屬性為showtxt的div標簽。find_all方法的第一個參數是獲取的標簽名,第二個參數class_是標簽的屬性,為什么不是class,而帶了一個下劃線呢?因為python中class是關鍵字,為了防止沖突,這里使用class_表示標簽的class屬性,class_后面跟著的showtxt就是屬性值了。看下我們要匹配的標簽格式:
<div id="content", class="showtxt">
這樣對應的看一下,是不是就懂了?可能有人會問了,為什么不是find_all('div', id = 'content', class_ = 'showtxt')?這樣其實也是可以的,屬性是作為查詢時候的約束條件,添加一個class_='showtxt'條件,我們就已經能夠準確匹配到我們想要的標簽了,所以我們就不必再添加id這個屬性了。運行代碼查看我們匹配的結果:
我們可以看到,我們已經順利匹配到我們關心的正文內容,但是還有一些我們不想要的東西。比如div標簽名,br標簽,以及各種空格。怎么去除這些東西呢?我們繼續編寫代碼:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url = target)
html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt')
print(texts[0].text.replace('\xa0'*8,'\n\n'))
find_all匹配的返回的結果是一個列表。提取匹配結果后,使用text屬性,提取文本內容,濾除br標簽。隨后使用replace方法,剔除空格,替換為回車進行分段。 在html中是用來表示空格的。replace('\xa0'*8,'\n\n')就是去掉下圖的八個空格符號,并用回車代替:
程序運行結果如下:
可以看到,我們很自然的匹配到了所有正文內容,并進行了分段。我們已經順利獲得了一個章節的內容,要想下載正本小說,我們就要獲取每個章節的鏈接。我們先分析下小說目錄:
URL:http://www.biqukan.com/1_1094/
通過審查元素,我們發現可以發現,這些章節都存放在了class屬性為listmain的div標簽下,選取部分html代碼如下:
<div class="listmain">
<dl>
<dt>《一念永恒》最新章節列表</dt>
<dd><a href="/1_1094/15932394.html">第1027章 第十道門</a></dd>
<dd><a href="/1_1094/15923072.html">第1026章 絕倫道法!</a></dd>
<dd><a href="/1_1094/15921862.html">第1025章 長生燈!</a></dd>
<dd><a href="/1_1094/15918591.html">第1024章 一目晶淵</a></dd>
<dd><a href="/1_1094/15906236.html">第1023章 通天道門</a></dd>
<dd><a href="/1_1094/15903775.html">第1022章 四大兇獸!</a></dd>
<dd><a href="/1_1094/15890427.html">第1021章 鱷首!</a></dd>
<dd><a href="/1_1094/15886627.html">第1020章 一觸即發!</a></dd>
<dd><a href="/1_1094/15875306.html">第1019章 魁祖的氣息!</a></dd>
<dd><a href="/1_1094/15871572.html">第1018章 絕望的魁皇城</a></dd>
<dd><a href="/1_1094/15859514.html">第1017章 我還是恨你!</a></dd>
<dd><a href="/1_1094/15856137.html">第1016章 從來沒有世界之門!</a></dd>
<dt>《一念永恒》正文卷</dt>
<dd><a href="/1_1094/5386269.html">外傳1 柯父。</a></dd>
<dd><a href="/1_1094/5386270.html">外傳2 楚玉嫣。</a></dd>
<dd><a href="/1_1094/5386271.html">外傳3 鸚鵡與皮凍。</a></dd>
<dd><a href="/1_1094/5403177.html">第一章 他叫白小純</a></dd>
<dd><a href="/1_1094/5428081.html">第二章 火灶房</a></dd>
<dd><a href="/1_1094/5433843.html">第三章 六句真言</a></dd>
<dd><a href="/1_1094/5447905.html">第四章 煉靈</a></dd>
</dl>
</div>
在分析之前,讓我們先介紹一個概念:父節點、子節點、孫節點。<div>和</div>限定了<div>標簽的開始和結束的位置,他們是成對出現的,有開始位置,就有結束位置。我們可以看到,在<div>標簽包含<dl>標簽,那這個<dl>標簽就是<div>標簽的子節點,<dl>標簽又包含<dt>標簽和<dd>標簽,那么<dt>標簽和<dd>標簽就是<div>標簽的孫節點。有點繞?那你記住這句話:誰包含誰,誰就是誰兒子!
他們之間的關系都是相對的。比如對于<dd>標簽,它的子節點是<a>標簽,它的父節點是<dl>標簽。這跟我們人是一樣的,上有老下有小。
看到這里可能有人會問,這有好多<dd>標簽和<a>標簽啊!不同的<dd>標簽,它們是什么關系啊?顯然,兄弟姐妹嘍!我們稱它們為兄弟結點。
好了,概念明確清楚,接下來,讓我們分析一下問題。我們看到每個章節的名字存放在了<a>標簽里面。<a>標簽還有一個href屬性。這里就不得不提一下<a> 標簽的定義了,<a> 標簽定義了一個超鏈接,用于從一張頁面鏈接到另一張頁面。<a> 標簽最重要的屬性是 href 屬性,它指示鏈接的目標。
我們將之前獲得的第一章節的URL和<a> 標簽對比看一下:
http://www.biqukan.com/1_1094/5403177.html
<a href="/1_1094/5403177.html">第一章 他叫白小純</a>
不難發現,<a> 標簽中href屬性存放的屬性值/1_1094/5403177.html是章節URLhttp://www.biqukan.com/1_1094/5403177.html的后半部分。其他章節也是如此!那這樣,我們就可以根據<a> 標簽的href屬性值獲得每個章節的鏈接和名稱了。
總結一下:小說每章的鏈接放在了class屬性為listmain的<div>標簽下的<a>標簽中。鏈接具體位置放在html->body->div->dl->dd->a的href屬性中。先匹配class屬性為listmain的<div>標簽,再匹配<a>標簽。編寫代碼如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/'
req = requests.get(url = target)
html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
print(div[0])
還是使用find_all方法,運行結果如下:
很順利,接下來再匹配每一個<a>標簽,并提取章節名和章節文章。如果我們使用Beautiful Soup匹配到了下面這個<a>標簽,如何提取它的href屬性和<a>標簽里存放的章節名呢?
<a href="/1_1094/5403177.html">第一章 他叫白小純</a>
方法很簡單,對Beautiful Soup返回的匹配結果a,使用a.get('href')方法就能獲取href的屬性值,使用a.string就能獲取章節名,編寫代碼如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
server = 'http://www.biqukan.com/'
target = 'http://www.biqukan.com/1_1094/'
req = requests.get(url = target)
html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
a_bf = BeautifulSoup(str(div[0]))
a = a_bf.find_all('a')
for each in a:
print(each.string, server + each.get('href'))
因為find_all返回的是一個列表,里邊存放了很多的<a>標簽,所以使用for循環遍歷每個<a>標簽并打印出來,運行結果如下。
最上面匹配的一千多章的內容是最新更新的12章節的鏈接。這12章內容會和下面的重復,所以我們要濾除,除此之外,還有那3個外傳,我們也不想要。這些都簡單地剔除就好。
(3)整合代碼
每個章節的鏈接、章節名、章節內容都有了。接下來就是整合代碼,將獲得內容寫入文本文件存儲就好了。編寫代碼如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests, sys
"""
類說明:下載《筆趣看》網小說《一念永恒》
Parameters:
無
Returns:
無
Modify:
2017-09-13
"""
class downloader(object):
def __init__(self):
self.server = 'http://www.biqukan.com/'
self.target = 'http://www.biqukan.com/1_1094/'
self.names = [] #存放章節名
self.urls = [] #存放章節鏈接
self.nums = 0 #章節數
"""
函數說明:獲取下載鏈接
Parameters:
無
Returns:
無
Modify:
2017-09-13
"""
def get_download_url(self):
req = requests.get(url = self.target)
html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
a_bf = BeautifulSoup(str(div[0]))
a = a_bf.find_all('a')
self.nums = len(a[15:]) #剔除不必要的章節,并統計章節數
for each in a[15:]:
self.names.append(each.string)
self.urls.append(self.server + each.get('href'))
"""
函數說明:獲取章節內容
Parameters:
target - 下載連接(string)
Returns:
texts - 章節內容(string)
Modify:
2017-09-13
"""
def get_contents(self, target):
req = requests.get(url = target)
html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt')
texts = texts[0].text.replace('\xa0'*8,'\n\n')
return texts
"""
函數說明:將爬取的文章內容寫入文件
Parameters:
name - 章節名稱(string)
path - 當前路徑下,小說保存名稱(string)
text - 章節內容(string)
Returns:
無
Modify:
2017-09-13
"""
def writer(self, name, path, text):
write_flag = True
with open(path, 'a', encoding='utf-8') as f:
f.write(name + '\n')
f.writelines(text)
f.write('\n\n')
if __name__ == "__main__":
dl = downloader()
dl.get_download_url()
print('《一年永恒》開始下載:')
for i in range(dl.nums):
dl.writer(dl.names[i], '一念永恒.txt', dl.get_contents(dl.urls[i]))
sys.stdout.write(" 已下載:%.3f%%" % float(i/dl.nums) + '\r')
sys.stdout.flush()
print('《一年永恒》下載完成')
很簡單的程序,單進程跑,沒有開進程池。下載速度略慢,喝杯茶休息休息吧。代碼運行效果如下圖所示:
(1)實戰背景
已經會爬取文字了,是不是感覺爬蟲還是蠻好玩的呢?接下來,讓我們進行一個進階實戰,了解一下反爬蟲。
URL:https://unsplash.com/
看一看這些優美的壁紙,這個網站的名字叫做Unsplash,免費高清壁紙分享網是一個堅持每天分享高清的攝影圖片的站點,每天更新一張高質量的圖片素材,全是生活中的景象作品,清新的生活氣息圖片可以作為桌面壁紙也可以應用于各種需要的環境。
看到這么優美的圖片,我的第一反應就是想收藏一些,作為知乎文章的題圖再好不過了。每張圖片我都很喜歡,批量下載吧,不多爬,就下載50張好了。
(2)實戰進階
我們已經知道了每個html標簽都有各自的功能。<a>標簽存放一下超鏈接,圖片存放在哪個標簽里呢?html規定,圖片統統給我放到<img>標簽中!既然這樣,我們截取就Unsplash網站中的一個<img>標簽,分析一下:
<img alt="Snow-capped mountain slopes under blue sky" src="https://images.unsplash.com/photo-1428509774491-cfac96e12253?dpr=1&auto=compress,format&fit=crop&w=360&h=240&q=80&cs=tinysrgb&crop=" class="cV68d" style="width: 220px; height: 147px;">
可以看到,<img>標簽有很多屬性,有alt、src、class、style屬性,其中src屬性存放的就是我們需要的圖片保存地址,我們根據這個地址就可以進行圖片的下載。
那么,讓我們先捋一捋這個過程:
我們信心滿滿地按照這個思路爬取Unsplash試一試,編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'https://unsplash.com/'
req = requests.get(url=target)
print(req.text)
按照我們的設想,我們應該能找到很多<img>標簽。但是我們發現,除了一些<script>標簽和一些看不懂的代碼之外,我們一無所獲,一個<img>標簽都沒有!跟我們在網站審查元素的結果完全不一樣,這是為什么?
答案就是,這個網站的所有圖片都是動態加載的!網站有靜態網站和動態網站之分,上一個實戰爬取的網站是靜態網站,而這個網站是動態網站,動態加載有一部分的目的就是為了反爬蟲。
對于什么是動態加載,你可以這樣理解:
我們知道化妝術學的好,賊厲害,可以改變一個人的容貌。相應的,動態加載用的好,也賊厲害,可以改變一個網站的容貌。
動態網站使用動態加載常用的手段就是通過調用JavaScript來實現的。怎么實現JavaScript動態加載,我們不必深究,我們只要知道,動態加載的JavaScript腳本,就像化妝術需要用的化妝品,五花八門。有粉底、口紅、睫毛膏等等,它們都有各自的用途。動態加載的JavaScript腳本也一樣,一個動態加載的網站可能使用很多JavaScript腳本,我們只要找到負責動態加載圖片的JavaScript腳本,不就找到我們需要的鏈接了嗎?
對于初學者,我們不必看懂JavaScript執行的內容是什么,做了哪些事情,因為我們有強大的抓包工具,它自然會幫我們分析。這個強大的抓包工具就是Fiddler:
URL:http://www.telerik.com/fiddler
PS:也可以使用瀏覽器自帶的Networks,但是我更推薦這個軟件,因為它操作起來更高效。
安裝方法很簡單,傻瓜式安裝,一直下一步即可,對于經常使用電腦的人來說,應該沒有任何難度。
這個軟件的使用方法也很簡單,打開軟件,然后用瀏覽器打開我們的目標網站,以Unsplash為例,抓包結果如下:
我們可以看到,上圖左側紅框處是我們的GET請求的地址,就是網站的URL,右下角是服務器返回的信息,我們可以看到,這些信息也是我們上一個程序獲得的信息。這個不是我們需要的鏈接,我們繼續往下看。
我們發現上圖所示的就是一個JavaScript請求,看右下側服務器返回的信息是一個json格式的數據。這里面,就有我們需要的內容。我們局部放大看一下:
這是Fiddler右側的信息,上面是請求的Headers信息,包括這個Javascript的請求地 址:http://unsplash.com/napi/feeds/home,其他信息我們先不管,我們看看下面的內容。里面有很多圖片的信息,包括圖片的id,圖片的大小,圖片的鏈接,還有下一頁的地址。這個腳本以json格式存儲傳輸的數據,json格式是一種輕量級的數據交換格式,起到封裝數據的作用,易于人閱讀和編寫,同時也易于機器解析和生成。這么多鏈接,可以看到圖片的鏈接有很多,根據哪個鏈接下載圖片呢?先別急,讓我們繼續分析:
在這個網站,我們可以按這個按鈕進行圖片下載。我們抓包分下下這個動作,看看發送了哪些請求。
https://unsplash.com/photos/1PrQ2mHW-Fo/download?force=true
https://unsplash.com/photos/JX7nDtafBcU/download?force=true
https://unsplash.com/photos/HCVbP3zqX4k/download?force=true
通過Fiddler抓包,我們發現,點擊不同圖片的下載按鈕,GET請求的地址都是不同的。但是它們很有規律,就是中間有一段代碼是不一樣的,其他地方都一樣。中間那段代碼是不是很熟悉?沒錯,它就是我們之前抓包分析得到json數據中的照片的id。我們只要解析出每個照片的id,就可以獲得圖片下載的請求地址,然后根據這個請求地址,我們就可以下載圖片了。那么,現在的首要任務就是解析json數據了。
json格式的數據也是分層的。可以看到next_page里存放的是下一頁的請求地址,很顯然Unsplash下一頁的內容,也是動態加載的。在photos下面的id里,存放著圖片的id,這個就是我們需要獲得的圖片id號。
怎么編程提取這些json數據呢?我們也是分步完成:
編寫代碼,嘗試獲取json數據:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
req = requests.get(url=target)
print(req.text)
很遺憾,程序報錯了,問題出在哪里?通過錯誤信息,我們可以看到SSL認證錯誤,SSL認證是指客戶端到服務器端的認證。一個非常簡單的解決這個認證錯誤的方法就是設置requests.get()方法的verify參數。這個參數默認設置為True,也就是執行認證。我們將其設置為False,繞過認證不就可以了?
有想法就要嘗試,編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
req = requests.get(url=target, verify=False)
print(req.text)
認證問題解決了,又有新問題了:
可以看到,我們GET請求又失敗了,這是為什么?這個網站反爬蟲的手段除了動態加載,還有一個反爬蟲手段,那就是驗證Request Headers。接下來,讓我們分析下這個Requests Headers:
我截取了Fiddler的抓包信息,可以看到Requests Headers里又很多參數,有Accept、Accept-Encoding、Accept-Language、DPR、User-Agent、Viewport-Width、accept-version、Referer、x-unsplash-client、authorization、Connection、Host。它們都是什么意思呢?
專業的解釋能說的太多,我挑重點:
Unsplash是根據哪個參數反爬蟲的呢?根據我的測試,是authorization。我們只要通過程序手動添加這個參數,然后再發送GET請求,就可以順利訪問了。怎么什么設置呢?還是requests.get()方法,我們只需要添加headers參數即可。編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
headers = {'authorization':'your Client-ID'}
req = requests.get(url=target, headers=headers, verify=False)
print(req.text)
headers參數值是通過字典傳入的。記得將上述代碼中your Client-ID換成諸位自己抓包獲得的信息。代碼運行結果如下:
皇天不負有心人,可以看到我們已經順利獲得json數據了,里面有next_page和照片的id。接下來就是解析json數據。根據我們之前分析可知,next_page放在了json數據的最外側,照片的id放在了photos->id里。我們使用json.load()方法解析數據,編寫代碼如下:
# -*- coding:UTF-8 -*-
import requests, json
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
headers = {'authorization':'your Client-ID'}
req = requests.get(url=target, headers=headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
print('下一頁地址:',next_page)
for each in html['photos']:
print('圖片ID:',each['id'])
解析json數據很簡單,跟字典操作一樣,就是字典套字典。json.load()里面的參數是原始的json格式的數據。程序運行結果如下:
圖片的ID已經獲得了,再通過字符串處理一下,就生成了我們需要的圖片下載請求地址。根據這個地址,我們就可以下載圖片了。下載方式,使用直接寫入文件的方法。
(3)整合代碼
每次獲取鏈接加一個1s延時,因為人在瀏覽頁面的時候,翻頁的動作不可能太快。我們要讓我們的爬蟲盡量友好一些。
# -*- coding:UTF-8 -*-
import requests, json, time, sys
from contextlib import closing
class get_photos(object):
def __init__(self):
self.photos_id = []
self.download_server = 'https://unsplash.com/photos/xxx/download?force=trues'
self.target = 'http://unsplash.com/napi/feeds/home'
self.headers = {'authorization':'your Client-ID'}
"""
函數說明:獲取圖片ID
Parameters:
無
Returns:
無
Modify:
2017-09-13
"""
def get_ids(self):
req = requests.get(url=self.target, headers=self.headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
for each in html['photos']:
self.photos_id.append(each['id'])
time.sleep(1)
for i in range(4):
req = requests.get(url=next_page, headers=self.headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
for each in html['photos']:
self.photos_id.append(each['id'])
time.sleep(1)
"""
函數說明:圖片下載
Parameters:
無
Returns:
無
Modify:
2017-09-13
"""
def download(self, photo_id, filename):
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}
target = self.download_server.replace('xxx', photo_id)
with closing(requests.get(url=target, stream=True, verify = False, headers = self.headers)) as r:
with open('%d.jpg' % filename, 'ab+') as f:
for chunk in r.iter_content(chunk_size = 1024):
if chunk:
f.write(chunk)
f.flush()
if __name__ == '__main__':
gp = get_photos()
print('獲取圖片連接中:')
gp.get_ids()
print('圖片下載中:')
for i in range(len(gp.photos_id)):
print(' 正在下載第%d張圖片' % (i+1))
gp.download(gp.photos_id[i], (i+1))
下載速度還行,有的圖片下載慢是因為圖片太大。可以看到右側也打印了一些警報信息,這是因為我們沒有進行SSL驗證。
學會了爬取圖片,簡單的動態加載的網站也難不倒你了。趕快試試國內的一些圖片網站吧!
(1)實戰背景
愛奇藝的VIP視頻只有會員能看,普通用戶只能看前6分鐘。比如加勒比海盜5:
URL:http://www.iqiyi.com/v_19rr7qhfg0.html#vfrm=19-9-0-1
我們怎么免費看VIP視頻呢?一個簡單的方法,就是通過旋風視頻VIP解析網站。
URL:http://api.xfsub.com/
這個網站為我們提供了免費的視頻解析,它的通用解析方式是:
http://api.xfsub.com/index.php?url=[播放地址或視頻id]
比如,對于繡春刀這個電影,我們只需要在瀏覽器地址欄輸入:
http://api.xfsub.com/index.php?url=http://www.iqiyi.com/v_19rr7qhfg0.html#vfrm=19-9-0-1
這樣,我們就可以在線觀看這些VIP視頻了:
但是這個網站只提供了在線解析視頻的功能,沒有提供下載接口,如果想把視頻下載下來,我們就可以利用網絡爬蟲進行抓包,將視頻下載下來。
(2)實戰升級
分析方法相同,我們使用Fiddler進行抓包:
我們可以看到,有用的請求并不多,我們逐條分析。我們先看第一個請求返回的信息。
可以看到第一個請求是GET請求,沒有什么有用的信息,繼續看下一條。
我們看到,第二條GET請求地址變了,并且在返回的信息中,我們看到,這個網頁執行了一個POST請求。POST請求是啥呢?它跟GET請求正好相反,GET是從服務器獲得數據,而POST請求是向服務器發送數據,服務器再根據POST請求的參數,返回相應的內容。這個POST請求有四個參數,分別為time、key、url、type。記住這個有用的信息,我們在抓包結果中,找一下這個請求,看看這個POST請求做了什么。
很顯然,這個就是我們要找的POST請求,我們可以看到POST請求的參數以及返回的json格式的數據。其中url存放的參數如下:
xfsub_api\/url.php?key=02896e4af69fb18f70129b6046d7c718&time=1505724557&url=http%3A%2F%2Fwww.iqiyi.com%2Fv_19rr7qhfg0.html&type=&xml=1
這個信息有轉義了,但是沒有關系,我們手動提取一下,變成如下形式:
xfsub_api/url.php?key=02896e4af69fb18f70129b6046d7c718&time=1505724557&url=http://www.iqiyi.com/v_19rr7qhfg0.html&type=&xml=1
我們已經知道了這個解析視頻的服務器的域名,再把域名加上:
http://api.xfsub.com/xfsub_api\url.php?key=02896e4af69fb18f70129b6046d7c718&time=1505724557&url=http://www.iqiyi.com/v_19rr7qhfg0.html&type=&xml=1
這里面存放的是什么東西?不會視頻解析后的地址吧?我們有瀏覽器打開這個地址看一下:
果然,我們可以看到視頻地址近在眼前啊,URL如下:
http://disp.titan.mgtv.com/vod.do?fmt=4&pno=1121&fid=1FEA2622E0BD9A1CA625FBE9B5A238A6&file=/c1/2017/09/06_0/1FEA2622E0BD9A1CA625FBE9B5A238A6_20170906_1_1_705.mp4
我們再打開這個視頻地址:
瞧,我們就這樣得到了這個視頻在服務器上的緩存地址。根據這個地址,我們就可以輕松下載視頻了。
PS:需要注意一點,這些URL地址,都是有一定時效性的,很快就會失效,因為里面包含時間信息。所以,各位在分析的時候,要根據自己的URL結果打開網站才能看到視頻。
接下來,我們的任務就是編程實現我們所分析的步驟,根據不同的視頻播放地址獲得視頻存放的地址。
現在梳理一下編程思路:
(3)編寫代碼
編寫代碼的時候注意一個問題,就是我們需要使用requests.session()保持我們的會話請求。簡單理解就是,在初次訪問服務器的時候,服務器會給你分配一個身份證明。我們需要拿著這個身份證去繼續訪問,如果沒有這個身份證明,服務器就不會再讓你訪問。這也就是這個服務器的反爬蟲手段,會驗證用戶的身份。
#-*- coding:UTF-8 -*-
import requests,re, json
from bs4 import BeautifulSoup
class video_downloader():
def __init__(self, url):
self.server = 'http://api.xfsub.com'
self.api = 'http://api.xfsub.com/xfsub_api/?url='
self.get_url_api = 'http://api.xfsub.com/xfsub_api/url.php'
self.url = url.split('#')[0]
self.target = self.api + self.url
self.s = requests.session()
"""
函數說明:獲取key、time、url等參數
Parameters:
無
Returns:
無
Modify:
2017-09-18
"""
def get_key(self):
req = self.s.get(url=self.target)
req.encoding = 'utf-8'
self.info = json.loads(re.findall('"url.php",\ (.+),', req.text)[0]) #使用正則表達式匹配結果,將匹配的結果存入info變量中
"""
函數說明:獲取視頻地址
Parameters:
無
Returns:
video_url - 視頻存放地址
Modify:
2017-09-18
"""
def get_url(self):
data = {'time':self.info['time'],
'key':self.info['key'],
'url':self.info['url'],
'type':''}
req = self.s.post(url=self.get_url_api,data=data)
url = self.server + json.loads(req.text)['url']
req = self.s.get(url)
bf = BeautifulSoup(req.text,'xml') #因為文件是xml格式的,所以要進行xml解析。
video_url = bf.find('file').string #匹配到視頻地址
return video_url
if __name__ == '__main__':
url = 'http://www.iqiyi.com/v_19rr7qhfg0.html#vfrm=19-9-0-1'
vd = video_downloader(url)
vd.get_key()
print(vd.get_url())
思路已經給出,希望喜歡爬蟲的人可以在運行下代碼之后,自己重頭編寫程序,因為只有經過自己分析和測試之后,才能真正明白這些代碼的意義。上述代碼運行結果如下:
我們已經順利獲得了mp4這個視頻文件地址。根據視頻地址,使用urllib.request.urlretrieve()即可將視頻下載下來。編寫代碼如下:
#-*- coding:UTF-8 -*-
import requests,re, json, sys
from bs4 import BeautifulSoup
from urllib import request
class video_downloader():
def __init__(self, url):
self.server = 'http://api.xfsub.com'
self.api = 'http://api.xfsub.com/xfsub_api/?url='
self.get_url_api = 'http://api.xfsub.com/xfsub_api/url.php'
self.url = url.split('#')[0]
self.target = self.api + self.url
self.s = requests.session()
"""
函數說明:獲取key、time、url等參數
Parameters:
無
Returns:
無
Modify:
2017-09-18
"""
def get_key(self):
req = self.s.get(url=self.target)
req.encoding = 'utf-8'
self.info = json.loads(re.findall('"url.php",\ (.+),', req.text)[0]) #使用正則表達式匹配結果,將匹配的結果存入info變量中
"""
函數說明:獲取視頻地址
Parameters:
無
Returns:
video_url - 視頻存放地址
Modify:
2017-09-18
"""
def get_url(self):
data = {'time':self.info['time'],
'key':self.info['key'],
'url':self.info['url'],
'type':''}
req = self.s.post(url=self.get_url_api,data=data)
url = self.server + json.loads(req.text)['url']
req = self.s.get(url)
bf = BeautifulSoup(req.text,'xml') #因為文件是xml格式的,所以要進行xml解析。
video_url = bf.find('file').string #匹配到視頻地址
return video_url
"""
函數說明:回調函數,打印下載進度
Parameters:
a b c - 返回信息
Returns:
無
Modify:
2017-09-18
"""
def Schedule(self, a, b, c):
per = 100.0*a*b/c
if per > 100 :
per = 1
sys.stdout.write(" " + "%.2f%% 已經下載的大小:%ld 文件大小:%ld" % (per,a*b,c) + '\r')
sys.stdout.flush()
"""
函數說明:視頻下載
Parameters:
url - 視頻地址
filename - 視頻名字
Returns:
無
Modify:
2017-09-18
"""
def video_download(self, url, filename):
request.urlretrieve(url=url,filename=filename,reporthook=self.Schedule)
if __name__ == '__main__':
url = 'http://www.iqiyi.com/v_19rr7qhfg0.html#vfrm=19-9-0-1'
vd = video_downloader(url)
filename = '加勒比海盜5'
print('%s下載中:' % filename)
vd.get_key()
video_url = vd.get_url()
print(' 獲取地址成功:%s' % video_url)
vd.video_download(video_url, filename+'.mp4')
print('\n下載完成!')
urlretrieve()有三個參數,第一個url參數是視頻存放的地址,第二個參數filename是保存的文件名,最后一個是回調函數,它方便我們查看下載進度。代碼量不大,很簡單,主要在于分析過程。代碼運行結果如下:
下載速度挺快的,幾分鐘視頻下載好了。
對于這個程序,感興趣的朋友可以進行擴展一下,設計出一個小軟件,根據用戶提供的url,提供PC在線觀看、手機在線觀看、視頻下載等功能。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。