Python——爬蟲

參考資料

  • 網絡爬蟲(又被稱爲網頁蜘蛛,網絡機器人,在FOAF社區中間,更經常的稱爲網頁追逐者),是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本。另外一些不常使用的名字還有螞蟻、自動索引、模擬程序或者蠕蟲。

    • 其實通俗的講就是通過程序去獲取web頁面上自己想要的數據,也就是自動抓取數據
  • 爬蟲的本質:模擬瀏覽器打開網頁,獲取網頁中我們想要的那部分數據

  • 瀏覽器打開網頁的過程:

    • 當你在瀏覽器中輸入地址後,經過DNS服務器找到服務器主機,向服務器發送一個請求,服務器經過解析後發送給用戶瀏覽器結果,包括html,js,css等文件內容,瀏覽器解析出來最後呈現給用戶在瀏覽器上看到的結果
    • 用戶看到的瀏覽器的結果就是由HTML代碼構成的,我們爬蟲就是爲了獲取這些內容,通過分析和過濾html代碼,從中獲取我們想要資源(文本,圖片,視頻.....)
  • 爬蟲就是請求網站並提取數據的自動化程序。其中請求提取自動化是爬蟲的關鍵!

爬蟲的基本流程

  • 發起請求
    • 通過HTTP庫向目標站點發起請求,也就是發送一個Request,請求可以包含額外的header等信息,等待服務器響應Response
  • 獲取響應內容
    • 如果服務器能正常響應,會得到一個Response,Response的內容便是所要獲取的頁面內容,類型可能是HTML,Json字符串,二進制數據(圖片或者視頻)等類型
  • 解析內容
    • 得到的內容可能是HTML,可以用正則表達式,頁面解析庫進行解析,可能是Json,可以直接轉換爲Json對象解析,可能是二進制數據,可以做保存或者進一步的處理
  • 保存數據
    • 保存形式多樣,可以存爲文本,也可以保存到數據庫,或者保存特定格式的文件

什麼是Request,Response

  • 瀏覽器發送消息給網址所在的服務器,這個過程就叫做HTPP Request
  • 服務器收到瀏覽器發送的消息後,能夠根據瀏覽器發送消息的內容,做相應的處理,然後把消息回傳給瀏覽器,這個過程就是HTTP Response
  • 瀏覽器收到服務器的Response信息後,會對信息進行相應的處理,然後展示

Request中包含什麼?

請求方式

主要有:GET/POST兩種類型常用,另外還有HEAD/PUT/DELETE/OPTIONS

  • GETPOST的區別就是:

    • GET是把參數數據隊列加到提交表單的ACTION屬性所指的URL中,值和表單內各個字段一一對應,在URL中可以看到。
    • POST是通過HTTP POST機制,將表單內各個字段放置在HTML HEADER內一起傳送到ACTION屬性所指的URL中(用戶看不到此過程)
    • GET:從服務器上獲取數據。使用GET方法應該只用在讀取數據,而不應當被用於產生“副作用”的操作中,例如在Web Application中。其中一個原因是GET可能會被網絡蜘蛛等隨意訪問。
    • POST:向指定資源提交數據,請求服務器進行處理(例如提交表單或者上傳文件)。
  • HEAD:與GET方法一樣,都是向服務器發出指定資源的請求。只不過服務器將不傳回資源的本文部分。它的好處在於,使用這個方法可以在不必傳輸全部內容的情況下,就可以獲取其中“關於該資源的信息”(元信息或稱元數據)。

  • PUT:向指定資源位置上傳其最新內容。

  • OPTIONS:這個方法可使服務器傳回該資源所支持的所有HTTP請求方法。用'*'來代替資源名稱,向Web服務器發送OPTIONS請求,可以測試服務器功能是否正常運作。

  • DELETE:請求服務器刪除Request-URI所標識的資源。

請求URL

URL,即統一資源定位符,也就是我們說的網址,統一資源定位符是對可以從互聯網上得到的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標準資源的地址。互聯網上的每個文件都有一個唯一的URL,它包含的信息指出文件的位置以及瀏覽器應該怎麼處理它。

URL的格式由三個部分組成:

  • 協議(或稱爲服務方式)。
  • 存有該資源的主機IP地址(有時也包括端口號)。
  • 主機資源的具體地址,如目錄和文件名等。

爬蟲爬取數據時必須要有一個目標的URL纔可以獲取數據,因此,它是爬蟲獲取數據的基本依據。

請求頭

包含請求時的頭部信息,如User-Agent,Host,Cookies等信息

請求體

請求攜帶的數據,如提交表單數據時候的表單數據(POST)

Response中包含了什麼

所有HTTP響應的第一行都是狀態行,依次是當前HTTP版本號,3位數字組成的狀態代碼,以及描述狀態的短語,彼此由空格分隔。

響應狀態

有多種響應狀態,如:
200代表成功,301跳轉,404找不到頁面,502服務器錯誤 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 服務器當前不能處理客戶端的請求,一段時間後可能恢復正常 301 目標永久性轉移 302 目標暫時性轉移

響應頭

如內容類型,類型的長度,服務器信息,設置Cookie

響應體

最主要的部分,包含請求資源的內容,如網頁HTMl,圖片,二進制數據等

能爬取什麼樣的數據

  • 網頁文本:如HTML文檔,Json格式化文本等
  • 圖片:獲取到的是二進制文件,保存爲圖片格式
  • 視頻:同樣是二進制文件
  • 其他:只要請求到的,都可以獲取

如何解析數據

  • 直接處理
  • Json解析
  • 正則表達式處理
  • BeautifulSoup解析處理
  • PyQuery解析處理
  • XPath解析處理
import urllib

Urllib庫的簡介

Urllib是python內置的HTTP請求庫,包括以下模塊

  • urllib.request: 請求模塊
  • urllib.error: 異常處理模塊
  • urllib.parse: url解析模塊
  • urllib.response: 響應請求
    • Type: module

    • Docstring:
      Response classes used by urllib.

    • The base class, addbase, defines a minimal file-like interface,including read() and readline().

    • The typical response object is an addinfourl instance, which defines an info() method that returns
      headers and a geturl() method that returns the url.

urllib.request

urllib.request.urlopen

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
參數:
Open the URL url, which can be either a string or a Request object.(url可以是一個網址的字符串,或是一個Request對象)
data must be an object specifying additional data to be sent to the server, or None if no such data is needed. See Request for details.


返回一個file-like對象(可以像操作context一樣進行管理和擁有一些實例方法):

   For HTTP and HTTPS URLs, this function returns a http.client.HTTPResponse
   object slightly modified. In addition to the three new methods above, the
   msg attribute contains the same information as the reason attribute ---
   the reason phrase returned by the server --- instead of the response
   headers as it is specified in the documentation for HTTPResponse.

   For FTP, file, and data URLs and requests explicitly handled by legacy
   URLopener and FancyURLopener classes, this function returns a
   urllib.response.addinfourl object.

   Note that None may be returned if no handler handles the request (though
   the default installed global OpenerDirector uses UnknownHandler to ensure
   this never happens).

   In addition, if proxy settings are detected (for example, when a *_proxy
   environment variable like http_proxy is set), ProxyHandler is default
   installed and makes sure the requests are handled through the proxy.

常用實例方法

  • geturl() - return the URL of the resource retrieved, commonly used to
    determine if a redirect was followed

  • info() - return the meta-information of the page, such as headers, in the
    form of an email.message_from_string() instance (see Quick Reference to
    HTTP Headers)

  • getcode() - return the HTTP status code of the response. Raises URLError
    on errors.

示例:

import urllib.request
response = urllib.request.urlopen('https://www.baidu.com')   # url是網址字符串
page = response.read()                # response.read()可以獲取到網頁的內容,獲取響應資源
decode_page = page.decode('utf-8')    # 統一編碼格式
print(decode_page)
<html>
<head>
	<script>
		location.replace(location.href.replace("https://","http://"));
	</script>
</head>
<body>
	<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
import urllib
req = urllib.request.Request('https://www.baidu.com')
response = urllib.request.urlopen(req)   # url是一個Request對象
the_page = response.read().decode('utf-8')
print(the_page)
<html>
<head>
	<script>
		location.replace(location.href.replace("https://","http://"));
	</script>
</head>
<body>
	<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
對象名 類型
req urllib.request.Request
response http.client.HTTPResponse
response.read() bytes
response.info() http.client.HTTPMessage
the_page str
type(response.info())
http.client.HTTPMessage
print(response.geturl())     # 返回真實網址
print(response.getcode())    # 返回`HTTP`響應代碼
print(response.info())   # 返回從服務器傳回的`MIME`標籤頭
https://www.baidu.com
200
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 227
Content-Type: text/html
Date: Wed, 23 Aug 2017 11:44:38 GMT
Last-Modified: Mon, 21 Aug 2017 07:02:00 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BD_NOT_HTTPS=1; path=/; Max-Age=300
Set-Cookie: BIDUPSID=2BA9A96F1CBA72FD5B271C3376C4397B; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1503488678; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=0
X-Ua-Compatible: IE=Edge,chrome=1
Connection: close

urlopen一般常用的有三個參數,它的參數如下:urllib.requeset.urlopen(url,data,timeout)

  • data參數的使用
    上述的例子是通過請求的get請求獲得,下面使用urllib的post請求
    這裏通過http://httpbin.org/post網站演示(該網站可以作爲練習使用urllib的一個站點使用,可以
    模擬各種請求操作)。
# urlopen傳入Request對象
import urllib
url = 'http://httpbin.org/post'
values = {'name':'WHY',
         'localtion':'SDU',
         'language':'Python'}
data = bytes(urllib.parse.urlencode(values),encoding='utf8')    # 編碼工作
print(data)
req = urllib.request.Request(url,data)   # 發送請求並傳輸data表單
response = urllib.request.urlopen(req)   
the_page = response.read().decode('utf8')               # 讀取響應內容
print(the_page)
b'name=WHY&localtion=SDU&language=Python'
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "language": "Python", 
    "localtion": "SDU", 
    "name": "WHY"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Connection": "close", 
    "Content-Length": "38", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Python-urllib/3.6"
  }, 
  "json": null, 
  "origin": "112.17.235.178", 
  "url": "http://httpbin.org/post"
}
# 直接傳入data
import urllib.parse
import urllib.request

data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
print(data)
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read().decode('utf8'))
b'word=hello'
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "word": "hello"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Connection": "close", 
    "Content-Length": "10", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Python-urllib/3.6"
  }, 
  "json": null, 
  "origin": "112.17.235.178", 
  "url": "http://httpbin.org/post"
}
import urllib
data = {}
data['name'] = 'WHY'
data['location'] = 'SDU'
data['language'] = 'Python'
url_values = urllib.parse.urlencode(data)
print(url_values)
url = 'https://www.baidu.com'
full_url = url + '?'+ url_values
data = urllib.request.urlopen(full_url)
print(data.read().decode('utf8'))
name=WHY&location=SDU&language=Python
<html>
<head>
	<script>
		location.replace(location.href.replace("https://","http://"));
	</script>
</head>
<body>
	<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>

這裏就用到urllib.parse,通過bytes(urllib.parse.urlencode())可以將post數據進行轉換放到urllib.request.urlopendata參數中。這樣就完成了一次post請求。所以如果我們添加data參數的時候就是以post請求方式請求,如果沒有data參數就是get請求方式

  • timeout參數的使用
    在某些網絡情況不好或者服務器端異常的情況會出現請求慢的情況,或者請求異常,所以這個時候我們需要給
    請求設置一個超時時間,而不是讓程序一直在等待結果。例子如下:
import urllib.request

response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
print(response.read())
b'{\n  "args": {}, \n  "headers": {\n    "Accept-Encoding": "identity", \n    "Connection": "close", \n    "Host": "httpbin.org", \n    "User-Agent": "Python-urllib/3.6"\n  }, \n  "origin": "218.75.27.189", \n  "url": "http://httpbin.org/get"\n}\n'

我們需要對異常進行抓取,代碼更改爲

import socket
import urllib.request
import urllib.error

try:
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT')
TIME OUT

響應response

響應類型、狀態碼、響應頭
response.status、response.getheaders()、response.getheader("server"),獲取狀態碼以及頭部信息
response.read()獲得的是響應體的內容

import urllib.request

response = urllib.request.urlopen('http://www.cnblogs.com/zhaof/p/6910871.html')
print(type(response))
print(response.status)
print(response.getheaders())   # Return list of (header, value) tuples.
print(response.getheader("server"))
print(response.getheader('Date'))
<class 'http.client.HTTPResponse'>
200
[('Date', 'Tue, 22 Aug 2017 12:33:22 GMT'), ('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '34644'), ('Connection', 'close'), ('Vary', 'Accept-Encoding'), ('Cache-Control', 'private, max-age=10'), ('Expires', 'Tue, 22 Aug 2017 12:33:31 GMT'), ('Last-Modified', 'Tue, 22 Aug 2017 12:33:21 GMT'), ('X-UA-Compatible', 'IE=10'), ('X-Frame-Options', 'SAMEORIGIN')]
None
Tue, 22 Aug 2017 12:33:22 GMT

設置Headers

有很多網站爲了防止程序爬蟲爬網站造成網站癱瘓,會需要攜帶一些headers頭部信息才能訪問,最常見的有user-agent參數

給請求添加頭部信息,從而定製自己請求網站是時的頭部信息

from urllib import request, parse

url = 'http://httpbin.org/post'
headers = {
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
    'Host': 'httpbin.org'
}
dict = {
    'name': 'zhaofan'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "zhaofan"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Connection": "close", 
    "Content-Length": "12", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"
  }, 
  "json": null, 
  "origin": "112.17.235.178", 
  "url": "http://httpbin.org/post"
}

添加請求頭的第二種方式

這種添加方式有個好處是自己可以定義一個請求頭字典,然後循環進行添加

  • 實例化一個對象
  • 使用add_header方法添加Header
from urllib import request, parse

url = 'http://httpbin.org/post'
dict = {
    'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')    # 添加Header
response = request.urlopen(req)
print(response.read().decode('utf-8'))
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "Germey"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Connection": "close", 
    "Content-Length": "11", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"
  }, 
  "json": null, 
  "origin": "112.17.235.178", 
  "url": "http://httpbin.org/post"
}

異常處理

在很多時候我們通過程序訪問頁面的時候,有的頁面可能會出現錯誤,類似404,500等錯誤

from urllib import request,error

try:
    response = request.urlopen("http://pythonsite.com/1111.html")
except error.URLError as e:
    print(e.reason)
Not Found

HTTPErrorURLError的子類

  • URLError裏只有一個屬性:reason,即抓異常的時候只能打印錯誤信息,類似上面的例子
  • HTTPError裏有三個屬性:code,reason,headers,即抓異常的時候可以獲得code,reson,headers三個信息
import urllib
req = urllib.request.Request('http://www.baidu.com')
try:
    urllib.request.urlopen(req)
except urllib.error.URLError as e:
    print(e.reason)    # reason屬性包含一個錯誤號和一個錯誤信息

捕獲異常

from urllib import request,error
try:
    response = request.urlopen("http://pythonsite.com/1111.html")
except error.HTTPError as e:
    print(e.reason)
    print(e.code)
    print(e.headers)
except error.URLError as e:
    print(e.reason)

else:
    print("reqeust successfully")
Not Found
404
Date: Wed, 23 Aug 2017 06:41:31 GMT
Server: Apache
Vary: Accept-Encoding
Content-Length: 207
Connection: close
Content-Type: text/html; charset=iso-8859-1
import urllib
req = urllib.request.Request('https://ai.taobao.com')
try:
    urllib.request.urlopen(req)
except urllib.error.URLError as e:
    print(e.code)
    print(e.read())       

e.reason其實也可以在做深入的判斷,例子如下:

import socket

from urllib import error,request

try:
    response = request.urlopen("http://www.pythonsite.com/",timeout=0.001)
except error.URLError as e:
    print(type(e.reason))
    if isinstance(e.reason,socket.timeout):
        print("time out")
<class 'socket.timeout'>
time out
import http
import pprint

pprint

Type:        module
Docstring:  
Support to pretty-print lists, tuples, & dictionaries recursively.

Very simple, but useful, especially in debugging data structures.

Classes
-------

PrettyPrinter()
    Handle pretty-printing operations onto a stream using a configured
    set of formatting parameters.

Functions
---------

pformat()
    Format a Python object into a pretty-printed representation.

pprint()
    Pretty-print a Python object to a stream [default is sys.stdout].

saferepr()
    Generate a 'standard' repr()-like value, but protect against recursive
    data structures.
pprint.pprint(http.HTTPStatus.PARTIAL_CONTENT)
<HTTPStatus.PARTIAL_CONTENT: 206>

高級用法各種handler

代理,ProxyHandler

通過rulllib.request.ProxyHandler()可以設置代理,網站它會檢測某一段時間某個IP 的訪問次數,如果訪問次數過多,它會禁止你的訪問,所以這個時候需要通過設置代理來爬取數據

import urllib
enable_proxy = True
proxy_handler = urllib.request.ProxyHandler({'http': 'http://192.168.1.1:8087'})    # 設置Proxy
null_proxy_handler = urllib.request.ProxyHandler({})   # 默認不設置Proxy
if enable_proxy:                                       # 需要設置Proxy的情況
    opener = urllib.request.build_opener(proxy_handler)
else:                                                  # 不需要設置Proxy的情況
    opener = urllib.request.build_opener(null_proxy_handler)
opener =urllib.request.install_opener(opener)

cookie,HTTPCookiProcessor

cookie中保存中我們常見的登錄信息,有時候爬取網站需要攜帶cookie信息訪問,這裏用到了http.cookijar,用於獲取cookie以及存儲cookie
更多內容可查看python_cookie

import http.cookiejar, urllib.request
cookie = http.cookiejar.CookieJar()          # 實例化Cookie對象
handler = urllib.request.HTTPCookieProcessor(cookie)   
opener = urllib.request.build_opener(handler)     # 建立opener
response = opener.open('http://www.baidu.com')    # 使用opener打開網頁
for item in cookie:                           # 連接成功後Cookie將存至opener綁定的cookie對象中
    print(item.name+"="+item.value)
BAIDUID=FD7AB8ACCA2E3FFFCC647B0C8A4F90AE:FG=1
BIDUPSID=FD7AB8ACCA2E3FFFCC647B0C8A4F90AE
H_PS_PSSID=1449_21094_18559_22159
PSTM=1503494060
BDSVRTM=0
BD_HOME=0

同時cookie可以寫入到文件中保存,有兩種方式http.cookiejar.MozillaCookieJar和http.cookiejar.LWPCookieJar()

# http.cookiejar.MozillaCookieJar()方式
import http.cookiejar, urllib.request
filename = "cookie.txt"
cookie = http.cookiejar.MozillaCookieJar(filename)    
handler = urllib.request.HTTPCookieProcessor(cookie)   
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
# http.cookiejar.LWPCookieJar()方式
import http.cookiejar, urllib.request
filename = 'cookie.txt'
cookie = http.cookiejar.LWPCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)

同樣的如果想要通過獲取文件中的cookie獲取的話可以通過load方式,當然用哪種方式寫入的,就用哪種方式讀取。

import http.cookiejar, urllib.request
cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
<html>
<head>
	<script>
		location.replace(location.href.replace("https://","http://"));
	</script>
</head>
<body>
	<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>

urllib.request.urlretrieve

urllib.request.urlretrieve(url, filename=None, reporthook=None, data=None)

Retrieve a URL into a temporary location on disk.

    Requires a URL argument. If a filename is passed, it is used as
    the temporary file location. The reporthook argument should be
    a callable that accepts a block number, a read size, and the
    total file size of the URL target. The data argument should be
    valid URL encoded data.

    If a filename is passed and the URL points to a local resource,
    the result is a copy from local file to new file.

    Returns a tuple containing the path to the newly created
    data file as well as the resulting HTTPMessage object.

參數

  • finename: 指定了保存本地路徑(如果參數未指定,urllib會生成一個臨時文件保存數據。)
  • reporthook: 是一個回調函數,當連接上服務器、以及相應的數據塊傳輸完畢時會觸發該回調,我們可以利用這個回調函數來顯示當前的下載進度。
  • data: 指 post 到服務器的數據,該方法返回一個包含兩個元素的(filename, headers)元組,filename 表示保存到本地的路徑,header 表示服務器的響應頭。

詳細內容參見網絡編程初探

# 下面是 urlretrieve() 下載文件實例,可以顯示下載進度
import urllib
import os

def Schedule(a,b,c):
    '''
    回調函數
    @a: 已經下載的數據塊
    @b:數據塊大小
    @c:遠程文件大小
    '''
    per = 100.0 * a * b / c
    if per > 100:
        per = 100
    print('%.2f%%' % per)
    
url = 'https://cn.bing.com/images/search?view=detailV2&ccid=GW2QgFkz&id=EF611ABD160281C674AE77A4287DBCDB14A334E5&thid=OIP.GW2QgFkzmTMDJuwkKOiNbwEsEp&q=%e5%9b%be%e7%89%87&simid=608041670019188634&selectedIndex=8'
local = os.path.join(r'E:\圖片','美女.jpg')
urllib.request.urlretrieve(url,local,Schedule)
0.00%
7.95%
15.90%
23.85%
31.79%
39.74%
47.69%
55.64%
63.59%
71.54%
79.48%
87.43%
95.38%
100.00%





('E:\\圖片\\美女.jpg', <http.client.HTTPMessage at 0x18b73720400>)

URL解析

urlparse

urllib.parse.urlparse(url, scheme='', allow_fragments=True)

就是可以對你傳入的url地址進行拆分,同時我們是可以指定協議類型:
result = urlparse("www.baidu.com/index.html;user?id=5#comment",scheme="https")
這樣拆分的時候協議類型部分就會是你指定的部分,當然如果你的url裏面已經帶了協議,你再通過scheme指定的協議就不會生效

from urllib.parse import urlparse

result = urlparse("http://www.baidu.com/index.html;user?id=5#comment")
print(result)
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')

urlunpars

其實功能和urlparse的功能相反,它是用於拼接,例子如下:

from urllib.parse import urlunparse

data = ['http','www.baidu.com','index.html','user','a=123','commit']
print(urlunparse(data))
http://www.baidu.com/index.html;user?a=123#commit

urljoin

這個的功能其實是做拼接的(從拼接的結果我們可以看出,拼接的時候後面的優先級高於前面的url),例子如下:

from urllib.parse import urljoin

print(urljoin('http://www.baidu.com', 'FAQ.html'))
print(urljoin('http://www.baidu.com', 'https://pythonsite.com/FAQ.html'))
print(urljoin('http://www.baidu.com/about.html', 'https://pythonsite.com/FAQ.html'))
print(urljoin('http://www.baidu.com/about.html', 'https://pythonsite.com/FAQ.html?question=2'))
print(urljoin('http://www.baidu.com?wd=abc', 'https://pythonsite.com/index.php'))
print(urljoin('http://www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com#comment', '?category=2'))
http://www.baidu.com/FAQ.html
https://pythonsite.com/FAQ.html
https://pythonsite.com/FAQ.html
https://pythonsite.com/FAQ.html?question=2
https://pythonsite.com/index.php
http://www.baidu.com?category=2#comment
www.baidu.com?category=2#comment
www.baidu.com?category=2

urlencode

這個方法可以將字典轉換爲url參數,例子如下

from urllib.parse import urlencode

params = {
    "name":"zhaofan",
    "age":23,
}
base_url = "http://www.baidu.com?"

url = base_url+urlencode(params)
print(url)
http://www.baidu.com?name=zhaofan&age=23

一個簡單的爬蟲例子

import string,urllib
import re,os
import string,urllib
import re,os

seed = 'https://zhuanlan.zhihu.com/p/25063314'    # 最開始的種子
depth = 3       # 最多遞歸depth層,避免遞歸棧過深
count = 5          # 每個網頁只抓取count個URL作爲新的seed
href_re = re.compile(r'href\s*=\s*"(https?://\S*)"')     # 通過正則匹配網頁源碼中的URL
http_re = re.compile(r'\w+')         # 通過正則匹配URL中的文字部分
pages = set()                   # 已經爬過的URL

save_dir = '.'             # 保存路徑
def get_path(url):
    '''
    通過url獲取保存文件路徑,使用'_'拼接url中的文字部分
    爲避免文件名過長,只取拼接後字符串的前30個字符
    '''
    name = '_'.join(http_re.findall(url))[:30]
    return os.path.join(save_dir,'%s.txt'%name)

def fetch(que = [seed,],dep = 0):
    '''
    深度優先搜索爬取que列表中的url,並選取網頁內容中的count個url作爲新的seed
    fetch函數最多遞歸depth層
    '''
    nxt_que = []                  # 下一層遞歸所用到的seed列表
    for url in que:
        print('depth: %d     fetching %s ...'%(dep,url))
        html = urllib.request.urlopen(url).read()
        html = str(html)
        with open(get_path(url),'w+') as f:
            f.write(html)         # 保存網頁內容
        
        cnt = 0   # 新的seed的計數
        for new_url in href_re.findall(html):
            if new_url in pages: continue     # 若已經爬過,則跳過
            pages.add(new_url)
            cnt += 1
            nxt_que.append(new_url)     # 將新的seed放入nxt_que中
            if cnt >= count:            # 最多選count個
                break
                
    if dep < depth:
        fetch(nxt_que,dep + 1)
        

if __name__ == '__main__':
    fetch()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章