1. 程式人生 > >Python之網路請求

Python之網路請求

一、爬蟲簡介

1、爬蟲的實際例子

(1)搜尋引擎(百度、谷歌、360搜尋等)。
(2)伯樂線上。
(3)惠惠購物助手。
(4)資料分析與研究(資料冰山知乎專欄)。
(5)搶票軟體等。

2、什麼是網路爬蟲

(1)通俗理解:爬蟲是一個模擬人類請求網站行為的程式。可以自動請求網頁、並資料抓取下來,然後使用一定的規則提取有價值的資料

(2)專業介紹:百度百科

3、通用爬蟲和聚焦爬蟲

(1)通用爬蟲:通用爬蟲是搜尋引擎抓取系統(百度、谷歌、搜狗等)的重要組成部分。主要是將網際網路上的網頁下載到本地,形成一個網際網路內容的映象備份。

(2)聚焦爬蟲:是面向特定需求的一種網路爬蟲程式,他與通用爬蟲的區別在於:聚焦爬蟲在實施網頁抓取的時候會對內容進行篩選和處理,儘量保證只抓取與需求相關的網頁資訊。

4、為什麼用Python寫爬蟲程式

(1)PHP:PHP是世界是最好的語言,但他天生不是做這個的,而且對多執行緒、非同步支援不是很好,併發處理能力弱。爬蟲是工具性程式,對速度和效率要求比較高。

(2)Java:生態圈很完善,是Python爬蟲最大的競爭對手。但是Java語言本身很笨重,程式碼量很大。重構成本比較高,任何修改會導致程式碼大量改動。爬蟲經常要修改採集程式碼。

(3)C/C++:執行效率是無敵的。但是學習和開發成本高。寫個小爬蟲程式可能要大半天時間。

(4)Python:語法優美、程式碼簡潔、開發效率高、支援的模組多。相關的HTTP請求模組和HTML解析模組非常豐富。還有Scrapy和Scrapy-redis框架讓我們開發爬蟲變得異常簡單。

5、準備工具

(1)Python3.6開發環境
(2)Pycharm 2017 professional版
(3)虛擬環境。`virtualenv/virtualenvwrapper

二、http協議和Chrome瀏覽器

1、什麼是http和https協議

HTTP協議:全稱是HyperText Transfer Protocol,中文意思是超文字傳輸協議,是一種釋出和接收HTML頁面的方法。伺服器埠號是80埠。 HTTPS協議:是HTTP協議的加密版本,在HTTP下加入了SSL層。伺服器埠號是443埠。

2、在瀏覽器中傳送一個http請求的過程

(1)當用戶在瀏覽器的位址列中輸入一個URL並按回車鍵之後,瀏覽器會向HTTP伺服器傳送HTTP請求。HTTP請求主要分為“Get”和“Post”兩種方法

(2)當我們在瀏覽器輸入URL http://www.baidu.com 的時候,瀏覽器傳送一個Request請求去獲取 http://www.baidu.com 的html檔案,伺服器把Response檔案物件傳送回給瀏覽器

(3)瀏覽器分析Response中的 HTML,發現其中引用了很多其他檔案,比如Images檔案,CSS檔案,JS檔案。 瀏覽器會自動再次傳送Request去獲取圖片,CSS檔案,或者JS檔案

(4)當所有的檔案都下載成功後,網頁會根據HTML語法結構,完整的顯示出來了

3、url詳解

URL是Uniform Resource Locator的簡寫,統一資源定位符。 一個URL由以下幾部分組成:

例如:scheme://host:port/path/?query-string=xxx#anchor

  • scheme:代表的是訪問的協議,一般為http或者https以及ftp等
  • host:主機名,域名,比如www.baidu.com
  • port:埠號。當你訪問一個網站的時候,瀏覽器預設使用80埠
  • path:查詢路徑。比如:www.jianshu.com/trending/now,後面的trending/now就是path
  • query-string:查詢字串,比如:www.baidu.com/s?wd=python,後面的wd=python就是查詢字串
  • anchor:錨點,後臺一般不用管,前端用來做頁面定位的

在瀏覽器中請求一個url,瀏覽器會對這個url進行一個編碼。除英文字母,數字和部分符號外,其他的全部使用百分號+十六進位制碼值進行編碼。

4、常用的請求方法

在Http協議中,定義了八種請求方法。這裡介紹兩種常用的請求方法,分別是get請求和post請求

(1)get請求:一般情況下,只從伺服器獲取資料下來,並不會對伺服器資源產生任何影響的時候會使用get請求

(2)post請求:向伺服器傳送資料(登入)、上傳檔案等,會對伺服器資源產生影響的時候會使用post請求。 以上是在網站開發中常用的兩種方法。並且一般情況下都會遵循使用的原則。但是有的網站和伺服器為了做反爬蟲機制,也經常會不按常理出牌,有可能一個應該使用get方法的請求就一定要改成post請求,這個要視情況而定

5、請求頭常見引數

在http協議中,向伺服器傳送一個請求,資料分為三部分,第一個是把資料放在url中,第二個是把資料放在body中(在post請求中),第三個就是把資料放在head中。這裡介紹在網路爬蟲中經常會用到的一些請求頭引數:

(1)User-Agent:瀏覽器名稱。這個在網路爬蟲中經常會被使用到。請求一個網頁的時候,伺服器通過這個引數就可以知道這個請求是由哪種瀏覽器傳送的。如果我們是通過爬蟲傳送請求,那麼我們的User-Agent就是Python,這對於那些有反爬蟲機制的網站來說,可以輕易的判斷你這個請求是爬蟲。因此我們要經常設定這個值為一些瀏覽器的值,來偽裝我們的爬蟲

(2)Referer:表明當前這個請求是從哪個url過來的。這個一般也可以用來做反爬蟲技術。如果不是從指定頁面過來的,那麼就不做相關的響應

(3)Cookie:http協議是無狀態的。也就是同一個人傳送了兩次請求,伺服器沒有能力知道這兩個請求是否來自同一個人。因此這時候就用cookie來做標識。一般如果想要做登入後才能訪問的網站,那麼就需要傳送cookie資訊了

6、常見響應狀態碼

(1)200:請求正常,伺服器正常的返回資料

(2)301:永久重定向。比如在訪問www.jingdong.com的時候會重定向到www.jd.com

(3)302:臨時重定向。比如在訪問一個需要登入的頁面的時候,而此時沒有登入,那麼就會重定向到登入頁面

(4)400:請求的url在伺服器上找不到。換句話說就是請求url錯誤

(5)403:伺服器拒絕訪問,許可權不夠

(6)500:伺服器內部錯誤。可能是伺服器出現bug了

三、urllib庫

1、urllib庫

urllib庫是Python中一個最基本的網路請求庫。可以模擬瀏覽器的行為,向指定的伺服器傳送一個請求,並可以儲存伺服器返回的資料

2、urlopen函式

在Python3的urllib庫中,所有和網路請求相關的方法,都被集到urllib.request模組下面了,以先來看下urlopen函式基本的使用:

from urllib import request
resp = request.urlopen('http://www.baidu.com')
print(resp.read())

實際上,使用瀏覽器訪問百度,右鍵檢視原始碼。你會發現,跟我們剛才打印出來的資料是一模一樣的。也就是說,上面的三行程式碼就已經幫我們把百度的首頁的全部程式碼爬下來了。一個基本的url請求對應的python程式碼真的非常簡單

以下對urlopen函式的進行詳細講解:
(1)url:請求的url
(2)data:請求的data,如果設定了這個值,那麼將變成post請求
(3)返回值:返回值是一個http.client.HTTPResponse物件,這個物件是一個類檔案控制代碼物件。有read(size)、readline、readlines以及getcode等方法

3、urlretrieve函式

這個函式可以方便的將網頁上的一個檔案儲存到本地。以下程式碼可以非常方便的將百度的首頁下載到本地:

from urllib import request

# 將百度首頁下載到本地
request.urlretrieve('http://www.baidu.com','baidu.html')

# 下載圖片
request.urlretrieve('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533566044817&di=7692eeb3301d0433a8e0f8d73da19b0e&imgtype=0&src=http%3A%2F%2Fu.candou.com%2F2017%2F0613%2F1497342507815.jpg'
    ,'luban.jpg')

4、urlencode函式

用瀏覽器傳送請求的時候,如果url中包含了中文或者其他特殊字元,那麼瀏覽器會自動的給我們進行編碼。而如果使用程式碼傳送請求,那麼就必須手動的進行編碼,這時候就應該使用urlencode函式來實現。urlencode可以把字典資料轉換為URL編碼的資料。示例程式碼如下:

from urllib import parse
param = {'name':'張三','age':18,'greet':'Hello World'}
result = parse.urlencode(param)
print(result)

# 編碼並請求
url = 'https://www.baidu.com/s?ie=UTF-8&'
param = {'wd':'劉德華'}
result = parse.urlencode(param)
result = url + result
ps = request.urlopen(result)
print(ps.read())

5、parse_qs函式

可以將經過編碼後的url引數進行解碼。示例程式碼如下:

from urllib import parse
param = {'name':'張三','age':18,'greet':'Hello World'}
result = parse.urlencode(param);
qs = parse.parse_qs(result)
print(qs)

6、urlparse和urlsplit

有時候拿到一個url,想要對這個url中的各個組成部分進行分割,那麼這時候就可以使用urlparse或者是urlsplit來進行分割

from urllib import request,parse

url = 'http://www.baidu.com/s?username=zhiliao'
result1 = parse.urlsplit(url)
result2 = parse.urlparse(url)
print(result1)
print(result2)
print('scheme:',result1.scheme)
print('netloc:',result1.netloc)
print('path',result1.path)
print('query',result1.query)

urlparse和urlsplit基本上是一模一樣的。唯一不一樣的地方是,urlparse裡面多了一個params屬性,而urlsplit沒有這個params屬性。比如有一個url為:url = ‘http://www.baidu.com/s;hello?wd=python&username=abc#1‘,
那麼urlparse可以獲取到hello,而urlsplit不可以獲取到。url中的params也用得比較少

7、request.Request類

如果想要在請求的時候增加一些請求頭,那麼就必須使用request.Request類來實現。比如要增加一個User-Agent

from urllib import request

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
}
req = request.Request("http://www.baidu.com/",headers=headers)
resp = request.urlopen(req)
print(resp.read())

8、ProxyHandler處理器(代理設定)

很多網站會檢測某一段時間某個IP的訪問次數(通過流量統計,系統日誌等),如果訪問次數多的不像正常人,它會禁止這個IP的訪問。
所以我們可以設定一些代理伺服器,每隔一段時間換一個代理,就算IP被禁止,依然可以換個IP繼續爬取。
urllib中通過ProxyHandler來設定使用代理伺服器

"""
# 沒有使用代理
url = 'http://httpbin.org/ip'
resp = request.urlopen(url)
print(resp.read())
"""

# 使用代理

url = 'http://httpbin.org/ip'
# 1、使用ProxyHandler,傳入代理構建一個handler
handler = request.ProxyHandler({"http":"115.46.73.180:8123"})
# 2、使用上面建立的handler構建一個opener
opener = request.build_opener(handler)
# 3、使用opener去傳送一個請求
resp = opener.open(url)
print(resp.read())

9、什麼是cookie

在網站中,http請求是無狀態的。也就是說即使第一次和伺服器連線後並且登入成功後,第二次請求伺服器依然不能知道當前請求是哪個使用者。cookie的出現就是為了解決這個問題,第一次登入後伺服器返回一些資料(cookie)給瀏覽器,然後瀏覽器儲存在本地,當該使用者傳送第二次請求的時候,就會自動的把上次請求儲存的cookie資料自動的攜帶給伺服器,伺服器通過瀏覽器攜帶的資料就能判斷當前使用者是哪個了。cookie儲存的資料量有限,不同的瀏覽器有不同的儲存大小,但一般不超過4KB。因此使用cookie只能儲存一些小量的資料。

cookie格式:

Set-Cookie: NAME=VALUE;Expires/Max-age=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE

引數意義:

  • NAME:cookie的名字
  • VALUE:cookie的值
  • Expires:cookie的過期時間
  • Path:cookie作用的路徑
  • Domain:cookie作用的域名
  • SECURE:是否只在https協議下起作用

10、使用cookielib庫和HTTPCookieProcessor模擬登入

Cookie 是指網站伺服器為了辨別使用者身份和進行Session跟蹤,而儲存在使用者瀏覽器上的文字檔案,Cookie可以保持登入資訊到使用者下次與伺服器的會話。
這裡以人人網為例。人人網中,要訪問某個人的主頁,必須先登入才能訪問,登入說白了就是要有cookie資訊。那麼如果我們想要用程式碼的方式訪問,就必須要有正確的cookie資訊才能訪問。解決方案有兩種,第一種是使用瀏覽器訪問,然後將cookie資訊複製下來,放到headers中

from urllib import request

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
    'Cookie': 'anonymid=jacdwz2x-8bjldx; depovince=GW; _r01_=1; _ga=GA1.2.1455063316.1511436360; _gid=GA1.2.862627163.1511436360; wp=1; JSESSIONID=abczwY8ecd4xz8RJcyP-v; jebecookies=d4497791-9d41-4269-9e2b-3858d4989785|||||; ick_login=884e75d4-f361-4cff-94bb-81fe6c42b220; _de=EA5778F44555C091303554EBBEB4676C696BF75400CE19CC; p=61a3c7d0d4b2d1e991095353f83fa2141; first_login_flag=1; [email protected]; ln_hurl=http://hdn.xnimg.cn/photos/hdn121/20170428/1700/main_nhiB_aebd0000854a1986.jpg; t=3dd84a3117737e819dd2c32f1cdb91d01; societyguester=3dd84a3117737e819dd2c32f1cdb91d01; id=443362311; xnsid=169efdc0; loginfrom=syshome; ch_id=10016; jebe_key=9c062f5a-4335-4a91-bf7a-970f8b86a64e%7Ca022c303305d1b2ab6b5089643e4b5de%7C1511449232839%7C1; wp_fold=0'
}

url = 'http://www.renren.com/880151247/profile'

req = request.Request(url,headers=headers)
resp = request.urlopen(req)
with open('renren.html','w') as fp:
    fp.write(resp.read().decode('utf-8'))

但是每次在訪問需要cookie的頁面都要從瀏覽器中複製cookie比較麻煩。在Python處理Cookie,一般是通過http.cookiejar模組和urllib模組的HTTPCookieProcessor處理器類一起使用。http.cookiejar模組主要作用是提供用於儲存cookie的物件。而HTTPCookieProcessor處理器主要作用是處理這些cookie物件,並構建handler物件。

http.cookiejar模組:

該模組主要的類有CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。這四個類的作用分別如下:

(1)CookieJar:管理HTTP cookie值、儲存HTTP請求生成的cookie、向傳出的HTTP請求新增cookie的物件。整個cookie都儲存在記憶體中,對CookieJar例項進行垃圾回收後cookie也將丟失。

(2)FileCookieJar (filename,delayload=None,policy=None):從CookieJar派生而來,用來建立FileCookieJar例項,檢索cookie資訊並將cookie儲存到檔案中。filename是儲存cookie的檔名。delayload為True時支援延遲訪問訪問檔案,即只有在需要時才讀取檔案或在檔案中儲存資料。

(3)MozillaCookieJar (filename,delayload=None,policy=None):從FileCookieJar派生而來,建立與Mozilla瀏覽器 cookies.txt相容的FileCookieJar例項

(4)LWPCookieJar (filename,delayload=None,policy=None):從FileCookieJar派生而來,建立與libwww-perl標準的 Set-Cookie3 檔案格式相容的FileCookieJar例項

11、登入人人網

利用http.cookiejar和request.HTTPCookieProcessor登入人人網


# 使用cookieJar訪問人人網

from urllib import request
from http.cookiejar import CookieJar
from urllib import parse

headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',

}
def get_opener():
    # 1、登陸
    # 1.1 建立一個cookiejar物件
    cookiejar = CookieJar()
    # 1.2 使用cookiejar建立一個HTTPCookieProcess物件
    handler = request.HTTPCookieProcessor(cookiejar)
    # 1.3 使用上一步建立的handler建立一個opener
    opener = request.build_opener(handler)
    return opener

def login_renren(opener):
    # 1.4 使用opener傳送登陸請求(包含人人網的郵箱密碼)
    data = {
        'email':"[email protected]",
        'password':"pythonspider"
    }
    login_url = "http://www.renren.com/PLogin.do"
    req = request.Request(url=login_url,data=parse.urlencode(data).encode('utf-8'),headers=headers)
    opener.open(req)


def visit_profile(opener):
    # 2、訪問個人主頁
    depeng_url = "http://www.renren.com/880151247/profile"
    req = request.Request(depeng_url,headers=headers)
    # 獲取個人主頁的頁面的時候,不要新建一個opener
    # 而是應該使用之前的opener,因為之前的那個opener
    # 包含了登陸的cookie
    resp = opener.open(req)
    with open('renren.html','w',encoding='utf-8') as fp:
        fp.write(resp.read().decode('utf-8'))


if __name__ == '__main__':
    opener = get_opener()
    login_renren(opener)
    visit_profile(opener)

12、儲存cookie到本地

儲存cookie到本地,可以使用cookiejar的save方法,並且需要指定一個檔名

# 將cookie資訊儲存到本地檔案
from urllib import request
from http.cookiejar import MozillaCookieJar

# # 指定儲存到cookie.txt檔案中
cookiejar = MozillaCookieJar('cookie.txt')

handler = request.HTTPCookieProcessor(cookiejar)
opener = request.build_opener(handler)

resp = opener.open('http://www.baidu.com')

# ignore_discard=True 將過期的cookie資訊也儲存
cookiejar.save(ignore_discard=True)

從本地檔案中獲取cookie資訊:

from urllib import request
from http.cookiejar import MozillaCookieJar
# 從本地檔案中讀取cookie資訊
cookiejar2 = MozillaCookieJar('cookie.txt')
# 匯入cookie(包含過期的)
cookiejar2.load(ignore_discard=True)
handler2 = request.HTTPCookieProcessor(cookiejar2)
opener2 = request.build_opener(handler2)
# 迴圈列印cookie資訊
for cookie in cookiejar2:
    print(cookie)

四、request庫

相關推薦

Python網路請求

一、爬蟲簡介 1、爬蟲的實際例子 (1)搜尋引擎(百度、谷歌、360搜尋等)。 (2)伯樂線上。 (3)惠惠購物助手。 (4)資料分析與研究(資料冰山知乎專欄)。 (5)搶票軟體等。 2、什麼是網路爬蟲 (1)通俗理解:爬蟲是一個模擬人類請求

聰哥哥教你學Python網路程式設計

網路程式設計,又稱Socket程式設計。 說到網路程式設計,大家都想起一個東西,那就是TCP/IP。 絕大多數程式語言都有對TCP/IP的操作API。 聰哥哥我今天主要圍繞兩個方面談談網路程式設計。一個TCP,另外一個就是UDP。 關於TCP和UDP,它們無論是在Java,還是在Py

python網路請求

原文連結:https://www.jianshu.com/p/f05d33475c78 urllib是Python中請求url連線的官方標準庫,在Python2中主要為urllib和urllib2,在Python3中整合成了urllib。 而urllib3則是增加了連線池等功能,兩者互相都有

Python——網路程式設計

轉載請註明出處:https://blog.csdn.net/l1028386804/article/details/83046191 一、C/S架構 客戶端/服務端架構 二、OSI七層架構 七層模型,亦稱OSI(Open System Interconnection)參考模型,是

python網路程式設計1-socket-tcp與udp

一、概念 兩個獨立python檔案之間可以通過寫入檔案,讀取檔案進行互動 由於不同機器上的程式要通訊,才產生了網路 B/S架構統一入口,各種小程式和公眾號 服務端 一直執行,等待服務別人 客戶端 用的時候,才使用服務 想要實現通訊 網絡卡 和 網線 網絡卡上有全球唯一的mac地址(實體地址) 4

Volley 原始碼解析網路請求

Volley 是 Google 推出的一款網路通訊框架,非常適合資料量小、通訊頻繁的網路請求,支援併發、快取和容易擴充套件、除錯等;不過不太適合下載大檔案、大量資料的網路請求,因為volley在解析期間將響應放到記憶體中,我們可以使用okhttp或者系統提供的DownloadManager來下載檔案。

React-Native網路請求

一、GET介面 let url = 'https://news-at.zhihu.com/api/4/themes' fetch(url) .then((response) => response.json()) .then

Python網路程式設計學習筆記

一、TCP-IP協議  1、TCP/IP協議包含幾個層        如果按照四個層進行劃分:           &

python傳送網路請求

1、使用urllib模組 get請求: res = urlopen(url) from urllib.request import urlopen url = 'http://www.nnzhp.cn' print(urlopen(url))#返回http.client.HTTP

python爬蟲#網路請求requests庫

中文文件 http://docs.python-requests.org/zh_CN/latest/user/quickstart.html requests庫 雖然Python的標準庫中 urllib模組已經包含了平常我們使用的大多數功能,但是它的 API 使用起來讓人感覺不太好,而 Requests宣傳是

Android技術架構網路請求心路歷程(可收藏)

前言 Android架構 Android架構技術介紹 架構與設計 設計模式 重構 網路程式設計框架 TCP格式三次握手與四次揮手 HttpClient HttpURLConnection Volley OkHttp Retr

Python網路程式設計(一)

CS架構 客戶端服務端架構 服務端:提供服務的 客戶端:享受服務的 BS架構:瀏覽器和服務端   網路通訊流程: 集線器:將所有連線上它的電腦全部聯通起來 交換機:升級版的集線器 網絡卡:接收處理電訊號 Mac地址:網路裝置的全球唯一標示,又稱為實體地址 廣播  單

Android肝帝戰紀網路請求框架封裝(Retrofit的封裝)

網路請求框架封裝(OkHttp3+Retrofit+loading的封裝) Retrofit的Github連結 點此連結到Github AVLoadingIndicatorView的Github連結(封裝到請求框架中,請求過程中的loading樣式框(

微信小程式開發網路請求(POST請求)

微信小程式開發中網路請求必不可少.GET.POST請求是最常用的.GET請求 POST請求的時候有好幾個坑.我已經為大家填好了. <img src="https://img-blog.csd

python介面請求

      實際工作中,需要用到python來對伺服器進行請求(也是方便進行介面自動化),因為,本文來記錄一下python是如何來進行get和post請求的,本文針對python的httplib模組介紹get和post請求,urllib模組直接進行請求 1、httplib模

python基礎網路程式設計--轉 python網路程式設計

python之網路程式設計 本地的程序間通訊(IPC)有很多種方式,但可以總結為下面4類: 訊息傳遞(管道、FIFO、訊息佇列) 同步(互斥量、條件變數、讀寫鎖、檔案和寫記錄鎖、訊號量) 共享

微信小程式開發網路請求(GET請求)

微信小程式開發中網路請求必不可少,今天說說最簡單的請求.後續會嘗試上傳下載,Socket這些. 1.一個微信小程式,同時只能有5個網路請求連線。 這個規定應該是微信為了保證使用者體驗制定的,畢竟是小程

Python網路程式設計(二)

  tcp三次握手和四次揮手       我們知道網路層,可以實現兩個主機之間的通訊。但是這並不具體,因為,真正進行通訊的實體是在主機中的程序,是一個主機中的一個程序與另外一個主機中的一個程序在交換資料。IP協議雖然能把資料報文送到目的主機,但是並沒有交付給主機的具體應用程序。而端到端的通訊才應該是應用程序

[程式碼筆記] python 網路程式設計: 使用udp 傳送音訊檔案

使用udp傳送一個可執行的jar檔案 服務端: #!python #檔名: demo_server.py ''' 規定: 伺服器接受客戶端請求,得知要傳送檔案了(實際就得知該傳送

flutter從零開始搭建(三) 網路請求

專案還是在原來的基礎上搭建,具體的可以看上面的連線 這次,我們來介紹下網路請求,並且將請求到的資料設定到ListView列表中。老規矩,先來看下效果圖 頁面看起來不錯吧,在動手之前還是得說一下,首頁資料來自wanandroid提供,畢竟用了別人的