Python教程
Python是一種簡單易學,功能強大的編程語言。它包括了高效的高級數據結構和簡單而有效的方法,面向對象編程。Python優雅的語法,動態類型,以及它天然的解釋能力,使其成為理想的語言,腳本和應用程序快速開發在大多數平臺上的許多領域。
Python解釋器及其擴展標準庫的源碼和編譯版本可以從Python的Web站點,http://www.python.org/所有主要平臺可自由查看,并且可以自由發布。該站點上也包含了分配和指針到很多免費的第三方Python模塊,程序,工具,以及附加的文檔。
Python的解釋器很容易擴展新的功能,并在C或C ++(或由C來調用其他語言)實現的數據類型。 Python也很適于作為定制應用的一種擴展語言。
本教程向讀者介紹了非正式的Python語言和系統的基本概念和功能。它有助于理解Python和實戰練習,當然所有的例子都是自包含的,所以這本手冊可以離線閱讀為好。
有關標準對象和模塊的詳細介紹,請參見Python標準庫。Python語言參考給出了語言的更正式的定義。需要編寫C或C + +擴展,請閱讀擴展和嵌入Python解釋器和Python/C的API參考手冊。也有幾本書涵蓋了各個深度的Python。
本教程并不試圖全面,涵蓋每一個功能,甚至每一個常用功能。相反,它介紹了許多Python中最引人注目的功能,會給Python語言的韻味和風格是一個好開始。看完之后,你就可以閱讀和編寫Python模塊和程序,將準備進一步了解Python標準庫描述的各種Python庫模塊。
Python概述
Python是一種高層次的,解釋性的,交互式和面向對象的腳本語言。Python被設計成具有很強的可讀性,它使用英語如其他語言常用空白作為標點符號,它比其他語言語法結構更少。
-
Python被解析:這意味著它是在運行時由解釋器處理,你并不需要在執行前編譯程序。這類似于Perl和PHP。
-
Python是互動:這意味著你可以在Python的提示和解釋器進行交互,直接寫出你的程序。
-
Python是面向對象的:這意味著Python支持面向對象的方式或程序,它封裝了對象中的代碼的技術。
-
Python是初學者的語言:Python是為初級程序員一種偉大的語言,并支持廣泛的應用,從簡單的文本處理,WWW瀏覽器,以游戲開發。
Python的歷史:
Python是由Guido van Rossum在八十年代末和九十年代初在全國研究所數學與計算機科學在荷蘭開發。
Python從許多其他語言,包括ABC,Modula-3語言,C語言,C+ +,Algol-68,Smalltalk和unix的shell等腳本語言得到參考開發。
Python是有版權的。比如Perl,Python源代碼現在是GNU通用公共許可證(GPL)下提供。
Python的現在是由一個核心開發團隊在維護,雖然Guido van Rossum仍然持有在指導其進展至關重要的作用。
Python的特點:
Python的功能亮點包括:
-
易于學習:Python有相對較少的關鍵字,結構簡單,明確的語法。這讓學生學習的時間相對較短。
-
易于閱讀:Python代碼是更加明確,可見。
-
易于維護:Python的成功在于它的源代碼是相當容易維護。
-
廣泛的標準庫:Python的最大優點是體積庫很方便,在UNIX,Windows和Macintosh跨平臺兼容。
-
交互模式:支持交互模式中,可以從終端輸入結果正確的語言,讓交互測試的代碼片段和調試。
-
便攜式:Python可以在多種硬件平臺上運行,并且對所有的平臺上使用相同的接口。
-
擴展:可以添加低級別的模塊在Python解釋器。這些模塊使程序員可以添加或自定義自己的工具來提高效率。
-
數據庫:Python提供接口給所有主要的商業數據庫。
-
GUI編程:Python支持,可以創建并移植到許多系統調用,庫和Windows系統,如Windows MFC,Macintosh和Unix的X Window系統的GUI應用程序。
-
可擴展性:Python提供了一個更好的結構,并支持比shell腳本大型程序。
除了上面提到的功能,Python也有很好的功能,幾個列舉如下:
-
支持功能和結構化的編程方法,以及面向對象。
-
它可以作為一種腳本語言,或者可以被編譯為字節碼建立大型的應用程序。
-
非常高的動態數據類型,并且支持動態類型檢查。
-
支持自動垃圾收集。
-
它可以用C,C + +,COM和ActiveX,CORBA和Java很容易地集成。
Python環境安裝
本地環境設置
如果愿意設置您的Python環境,讓我們了解如何建立Python環境。 Python可在各種平臺,包括Linux和Mac OS X,可嘗試打開一個終端窗口并輸入“python”,以檢查是否已經安裝了python,什么版本,如果已經有安裝。
-
Unix (Solaris, Linux, FreeBSD, AIX, HP/UX, SunOS, IRIX, etc.)
-
Win 9x/NT/2000
-
Macintosh (Intel, PPC, 68K)
-
OS/2
-
DOS (multiple versions)
-
PalmOS
-
Nokia 手機
-
Windows CE
-
Acorn/RISC OS
-
BeOS
-
Amiga
-
VMS/OpenVMS
-
QNX
-
VxWorks
-
Psion
-
Python也可被移植到Java和.NET 虛擬機
獲得Python
最新源代碼,二進制文件,文檔,新聞等可在Python的官方網站:
Python官方網站:http://www.python.org/
可以從以下站點下載Python文檔。文件格式是HTML,PDF和PostScript。
Python文檔網站:?www.python.org/doc/
安裝Python:
Python發行版適用于各種平臺。你只需要下載適用于您的平臺的二進制代碼并安裝Python。
如果二進制代碼針對您的平臺無法使用,你需要一個C編譯器來手動編譯源代碼。編譯源代碼提供了選擇,為安裝功能方面更大的靈活性。
這里是在各種平臺上安裝Python的快速概覽:
UNIX和Linux的安裝方式:
下面是簡單的步驟,在Unix/ Linux機器上安裝Python。
-
打開Web瀏覽器并轉至http://www.python.org/download/
-
按照鏈接下載壓縮的源代碼在Unix/ Linux操作系統。
-
下載并解壓文件。
-
編輯模塊/安裝文件,如果你想自定義一些選項。
-
執行./configure 腳本
-
make
-
make install
這將安裝python的標準位置在 /usr/local/bin目錄和它的庫安裝在/usr/local/lib/pythonXX,其中XX是Python使用的版本。
Windows上安裝:
下面是Windows機器上安裝Python的步驟。
-
打開Web瀏覽器并轉至?http://www.python.org/download/
-
按照鏈接到Windows安裝python-XYZ.msi文件,其中XYZ是你要安裝的版本。
-
要使用此安裝程序python-XYZ.msi,Windows系統必須支持Microsoft安裝程序2.0。只需安裝程序文件保存到本地計算機,然后運行它,看看是否你的機器支持MSI。
-
通過雙擊它在Windows中運行下載的文件。這將出Python的安裝向導,這些都很容易使用。只需接受默認設置,等到安裝完成后。
Macintosh上安裝:
最新的Mac電腦配備安裝了Python,但可能好幾年前的機器沒有安裝。見http://www.python.org/download/mac/上獲得的最新版本以及額外的工具來支持在Mac上開發的指令。對于老的Mac OS的Mac OS X10.3之前(2003年推出),MacPython上是可用的。“
只要到這個鏈接,完整Mac OS安裝安裝細節。
設置PATH:
程序和其他可執行文件可以住在許多目錄,所以操作系統提供,列出目錄的操作系統搜索可執行文件的搜索路徑。
路徑被存儲在環境變量,這是由操作系統維護的命名字符串。這些變量包含可用于命令行解釋器和其他程序的信息。
路徑變量名為Path的Unix或路徑在Windows(UNIX是區分大小寫的,Windows是沒有)。
在Mac OS中,安裝程序處理的道路細節。調用任何特定目錄Python解釋器,必須Python的目錄添加到您的路徑。
設置路徑,在Unix/Linux上:
將Python目錄添加到在Unix系統中的特定會話的路徑:
-
在csh shell:?輸入
SETENV?PATH "$PATH:/usr/local/bin/python" ?然后按回車鍵。 -
在 bash shell (Linux):?輸入
export PATH="$PATH:/usr/local/bin/python" 然后按回車鍵。 -
在 sh 或??ksh shell:?輸入?
PATH="$PATH:/usr/local/bin/python"?然后按回車鍵。
注:?/usr/local/bin/python 為Python目錄的路徑
設置路徑Windows系統:
以Python目錄添加到了 Windows 特定會話的路徑:
-
在命令提示符下:?輸入?
path %path%;C:\Python 然后按Enter鍵。
注意:C:\Python 是Python目錄的路徑
Python環境變量:
這里是重要的環境變量,其可以被Python確認:
變量 | 描述 |
---|---|
PYTHONPATH | 有類似路徑的作用。這個變量告訴Python解釋器在哪里可以找到導入到程序中的模塊文件。 PYTHONPATH應包括Python源代碼庫目錄,包含Python源代碼的目錄。 PYTHONPATH是由Python安裝程序有時會預設。 |
PYTHONSTARTUP | 包含了在每次啟動的解釋器(類似于Unix.profile或.login文件)時執行Python源代碼的初始化文件的路徑。這個文件通常命名為.pythonrc.py。在Unix中,通常包含加載實用程序或修改PYTHONPATH命令。 |
PYTHONCASEOK | 在Windows中使用,以指示Python找到一個import語句,第一個不區分大小寫的匹配。將此變量設置為任意值來激活它。 |
PYTHONHOME | 備選模塊搜索路徑。它通常嵌入在PYTHONSTARTUP或PYTHONPATH目錄,以使交換模塊庫的簡單。 |
運行Python:
有三種不同的方式來啟動Python:
(1) 交互式解釋器:
可以輸入python,并在開始通過命令行啟動在交互式解釋器它編碼的時候。從UNIX,DOS或其他系統提供了一個命令行解釋器或shell窗口。
$python # Unix/Linuxor python% # Unix/Linuxor C:>python # Windows/DOS
下面是所有可用的命令行選項的列表:
選項 | 描述 |
---|---|
-d | 提供調試輸出 |
-O | 生成優化代碼(結果為.pyo文件) |
-S | 不運行導入網站,在啟動時查找Python路徑 |
-v | 詳細輸出(在導入語句詳細的跟蹤) |
-X | 禁止基于類內置異常(只使用字符串);開始1.6版本過時 |
-c cmd | 作為cmd 字符串運行Python腳本發送 |
file | 從給定的文件運行Python腳本 |
(2) 腳本的命令行:
Python腳本可以在命令行中通過調用應用程序中的解釋,如下面的執行:
$python script.py # Unix/Linuxor python% script.py # Unix/Linuxor C:>python script.py # Windows/DOS
注意:請確保該文件的權限模式可以執行。
(3)集成開發環境
您可以從圖形用戶界面(GUI)環境中運行Python。所有需要的是一個支持Python系統的GUI應用程序。
-
UNIX:IDLE也是早期的UNIX系統為Python的IDE。
-
Windows:PythonWin是第一個Windows界面的Python和一個GUI的IDE。
-
Macintosh:Python的的Macintosh版本隨著閑置的IDE可從主站下載,不是MACBINARY就是BinHex'd文件。
Python基本語法
Python與Perl,C和Java語言等有許多相似之處。不過,也有語言之間有一些明確的區別。本章的目的是讓你迅速學習Python的語法。
第一個Python程序:
交互模式編程:
調用解釋器不經過腳本文件作為參數,顯示以下提示:
$ python Python 2.6.4 (#1, Nov 11 2014, 13:34:43) [GCC 4.1.2 20120704 (Red Hat 5.6.2-48)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
鍵入下列文字在Python提示符,然后按Enter鍵:
>>> print "Hello, Python!";
如果您運行的是新的Python版本,那么需要使用打印語句括號像print ("Hello, Python!");。但是在Python版本2.6.4,這將產生以下結果:
Hello, Python!
腳本模式編程:
調用解釋器及腳本作為參數,并開始執行的腳本,并一直持續到腳本完成。當腳本完成時,解釋器不再是活動的。
讓我們在腳本中編寫一個簡單的Python程序。所有的Python文件將具有.py擴展。所以,把下面的代碼寫在一個test.py文件。
print "Hello, Python!";
在這里,我假設你已經在PATH變量中設置Python解釋器。現在,嘗試如下運行這個程序:
$ python test.py
這將產生以下結果:
Hello, Python!
讓我們嘗試另一種方式來執行Python腳本。下面是修改后的test.py文件:
#!/usr/bin/pythonprint "Hello, Python!";
在這里,假設Python解釋器在/usr/bin目錄中可用。現在,嘗試如下運行這個程序:
$ chmod +x test.py # This is to make file executable $./test.py
這將產生以下結果:
Hello, Python!
Python標識符:
Python標識符是用來標識一個變量,函數,類,模塊或其他對象的名稱。一個標識符開始以字母A到Z或a?z或后跟零個或多個字母下劃線(_),下劃線和數字(0?9)。
Python中標識符內不允許標點符號,如@,$和%。 Python是一種區分大小寫的編程語言。因此,Manpower 和manpower在Python中是兩個不同的標識符。
這里有Python標識符命名約定:
-
類名以大寫字母以及所有其它標識符以小寫字母。
-
開頭單個前導下劃線的標識符表示由該標識符約定意思是私有的。
-
開頭兩個前導下劃線的標識符表示一個強烈的私有的標識符。
-
如果標識符末尾還具有兩個下劃線結束時,該標識符是一個語言定義的特殊名稱。
保留字:
下面列出了在Python中的保留字。這些保留字不可以被用作常量或變量,或任何其它標識符。所有Python關鍵字只包含小寫字母。
and | exec | not |
assert | finally | or |
break | for | pass |
class | from | |
continue | global | raise |
def | if | return |
del | import | try |
elif | in | while |
else | is | with |
except | lambda | yield |
行和縮進:
一個程序員學習Python時,遇到的第一個需要注意的地方是,不使用括號來表示代碼的類和函數定義塊或流程控制。代碼塊是由行縮進,這是嚴格執行表示方式。
在縮進位的數目是可變的,但是在塊中的所有語句必須縮進相同的量。在這個例子中,兩個功能塊都很好使用:
if True:print "True" else:print "False"
然而,在本實施例中的第二塊將產生一個錯誤:
if True:print "Answer"print "True" else:print "Answer"print "False"
因此,在Python中所有的連續線縮進的空格數同樣的會結成塊。以下是各種語句塊中的例子:
注意:不要試圖理解所使用的邏輯或不同的功能。只要確定你明白,即使他們各種模塊無需括號。
#!/usr/bin/pythonimport systry:# open file streamfile = open(file_name, "w") except IOError:print "There was an error writing to", file_namesys.exit() print "Enter '", file_finish, print "' When finished" while file_text != file_finish:file_text = raw_input("Enter text: ")if file_text == file_finish:# close the filefile.closebreakfile.write(file_text)file.write("\n") file.close() file_name = raw_input("Enter filename: ") if len(file_name) == 0:print "Next time please enter something"sys.exit() try:file = open(file_name, "r") except IOError:print "There was an error reading file"sys.exit() file_text = file.read() file.close() print file_text
多行語句:
Python語句通常用一個新行結束。 但是,Python允許使用續行字符(\)來表示,該行應該繼續下去(跨行)。例如:
total = item_one + \item_two + \item_three
包含在[],{}或()括號內的陳述并不需要使用續行符。例如:
days = ['Monday', 'Tuesday', 'Wednesday','Thursday', 'Friday']
Python引號:
Python接受單引號('),雙引號(“)和三(''或”“”)引用,以表示字符串常量,只要是同一類型的引號開始和結束的字符串。
三重引號可以用于跨越多個行的字符串。例如,所有下列是合法的:
word = 'word' sentence = "This is a sentence." paragraph = """This is a paragraph. It is made up of multiple lines and sentences."""
Python注釋:
一個井號(#),這不是一個字符串文字開頭的注釋。“#”號之后字符和到物理行是注釋的一部分,Python解釋器會忽略它們。
#!/usr/bin/python# First comment print "Hello, Python!"; # second comment
這將產生以下結果:
Hello, Python!
注釋可能會在聲明中表達或同一行之后:
name = "Madisetti" # This is again comment
你可以使用多行注釋如下:
# This is a comment. # This is a comment, too. # This is a comment, too. # I said that already.
使用空行:
一行只含有空格,可能帶有注釋,如果是空行那么Python完全忽略它。
在交互式解釋器會話中,必須輸入一個空的物理行終止多行語句。
等待用戶:
程序的下面一行顯示的提示,按回車鍵退出,等待用戶按下回車鍵:
#!/usr/bin/pythonraw_input("\n\nPress the enter key to exit.")
在這里,“\n\n已”被用來顯示實際行之前創建兩個換行。一旦用戶按下鍵時,程序結束。這是一個很好的技巧,保持一個控制臺窗口打開,直到用戶完成應用程序運行。
在一行中多個語句:
分號( ; ) 允許在單行寫入多條語句,不管語句是否啟動一個新的代碼塊。下面是使用分號示例:
import sys; x = 'foo'; sys.stdout.write(x + '\n')
多個語句組作為套件:
一組單獨的語句,在Python單一的代碼塊被稱為序列。復雜的語句,如if, while, def, and class,那些需要一個標題行和套件。
標題行開始的聲明(與關鍵字),并終止與冒號(:)),接著是一個或多個線構成該套件。例如:
if expression : suite elif expression : suite else : suite
命令行參數:
我們可能已經看到了,比如,很多程序可以運行,它們提供有關如何運行的一些基本信息。 Python中可以使用 -h 做到這一點:
$ python -h usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ... Options and arguments (and corresponding environment variables): -c cmd : program passed in as string (terminates option list) -d : debug output from parser (also PYTHONDEBUG=x) -E : ignore environment variables (such as PYTHONPATH) -h : print this help message and exit[ etc. ]
您也可以設定您的腳本,它應該以這樣的方式接受各種選項。??命令行參數是一個高級主題并在以后學習,當您通過其它的Python概念后。
Python變量類型
變量是只不過保留的內存位置用來存儲值。這意味著,當創建一個變量,那么它在內存中保留一些空間。
根據一個變量的數據類型,解釋器分配內存,并決定如何可以被存儲在所保留的內存中。因此,通過分配不同的數據類型的變量,你可以存儲整數,小數或字符在這些變量中。
變量賦值:
Python的變量不必顯式地聲明保留的存儲器空間。當分配一個值給一個變量的聲明將自動發生。等號(=)來賦值給變量。
操作數=操作符的左邊是變量,操作數=操作符的右側的名稱在變量中存儲的值。例如:
#!/usr/bin/pythoncounter = 100 # An integer assignment miles = 1000.0 # A floating point name = "John" # A stringprint counter print miles print name
在這里,分配值100,1000.0和“John”分別給變量counter,miles和respectively。當運行這個程序,這將產生以下結果:
100 1000.0 John
多重賦值:
Python允許您同時指定一個值給幾個變量。例如:
a = b = c = 1
這里,整數對象創建的值1,并且所有三個變量被分配到相同的內存位置。您也可以將多個對象分別到多個變量。例如:
a, b, c = 1, 2, "john"
這里,兩個整對象用值1和2分配給變量a和b,并且值為“john”的字符串對象被分配到變量c。
標準的數據類型:
存儲在內存中的數據可以是多種類型的。例如,一個人的年齡被存儲為一個數字值和他的地址被存儲為字母數字字符。Python用于對每個人的操作的各種標準類型定義在存儲方法。
Python有五個標準的數據類型:
-
數字
-
字符串
-
列表
-
元組
-
字典
Python數字:
數字數據類型存儲數值。它們是不可變的數據類型,這意味著改變一個新分配的對象的數字數據類型的結果值。
當分配一個值給他們創建的對象。例如:
var1 = 1 var2 = 10
也可以使用del語句刪去有關一些對象。 del語句的語法是:
del var1[,var2[,var3[....,varN]]]]
也可以使用del語句刪除單個或多個對象。例如:
del var del var_a, var_b
Python支持四種不同的數值類型:
-
int (有符號整數)
-
long (長整數[也可以以八進制和十六進制表示])
-
float (浮點實數值)
-
complex (復數)
例如:
這里是數字的一些例子:
int | long | float | complex |
---|---|---|---|
10 | 51924361L | 0.0 | 3.14j |
100 | -0x19323L | 15.20 | 45.j |
-786 | 0122L | -21.9 | 9.322e-36j |
080 | 0xDEFABCECBDAECBFBAEl | 32.3+e18 | .876j |
-0490 | 535633629843L | -90. | -.6545+0J |
-0x260 | -052318172735L | -32.54e100 | 3e+26J |
0x69 | -4721885298529L | 70.2-E12 | 4.53e-7j |
-
Python允許使用一個小寫L表示長整型,但建議您只使用一個大寫的L到避免和數字1 長得一樣不容易分辨,Python顯示長整數用一個大寫L。
-
復數包含一個有序對表示為a + bj,其中,a是實部,b是復數的虛部實浮點數。
Python字符串:
在Python中的字符串被確定為一組連續的字符在引號之間。 Python允許在任何對單引號或雙引號。串的子集,可以使用切片操作符可采用([]和[:]),索引從0開始的字符串的開始和結束(-1)。
加號(+)符號的字符串連接操作符,而星號(*)表示重復操作。例如:
#!/usr/bin/pythonstr = 'Hello World!'print str # Prints complete string print str[0] # Prints first character of the string print str[2:5] # Prints characters starting from 3rd to 5th print str[2:] # Prints string starting from 3rd character print str * 2 # Prints string two times print str + "TEST" # Prints concatenated string
這將產生以下結果:
Hello World! H llo llo World! Hello World!Hello World! Hello World!TEST
Python列表:
列表是最通用的Python復合數據類型。列表中包含以逗號分隔,并在方括號([])包含的項目。在一定程度上,列表相似C語言中的數組,它們之間的一個區別是,所有屬于一個列表中的項目可以是不同的數據類型的。
存儲在一個列表中的值可以使用切片操作符來訪問([]和[:])用索引從0開始,在列表的開始位置和結束為-1。加號(+)符號列表連接運算符,星號(*)重復操作。例如:
#!/usr/bin/pythonlist = [ 'abcd', 786 , 2.23, 'john', 70.2 ] tinylist = [123, 'john']print list # Prints complete list print list[0] # Prints first element of the list print list[1:3] # Prints elements starting from 2nd till 3rd print list[2:] # Prints elements starting from 3rd element print tinylist * 2 # Prints list two times print list + tinylist # Prints concatenated lists
這將產生以下結果:
['abcd', 786, 2.23, 'john', 70.200000000000003] abcd [786, 2.23] [2.23, 'john', 70.200000000000003] [123, 'john', 123, 'john'] ['abcd', 786, 2.23, 'john', 70.200000000000003, 123, 'john']
Python元組:
元組是類似于列表中的序列數據類型。一個元組由數個逗號分隔的值。不同于列表,不過,元組圓括號括起來。
列表和元組之間的主要區別是:列表括在括號([])和它們的元素和大小是可以改變的,而元組在圓括號(),不能被更新。元組可以被認為是只讀列表。例如:
#!/usr/bin/pythontuple = ( 'abcd', 786 , 2.23, 'john', 70.2 ) tinytuple = (123, 'john')print tuple # Prints complete list print tuple[0] # Prints first element of the list print tuple[1:3] # Prints elements starting from 2nd till 3rd print tuple[2:] # Prints elements starting from 3rd element print tinytuple * 2 # Prints list two times print tuple + tinytuple # Prints concatenated lists
這將產生以下結果:
('abcd', 786, 2.23, 'john', 70.200000000000003) abcd (786, 2.23) (2.23, 'john', 70.200000000000003) (123, 'john', 123, 'john') ('abcd', 786, 2.23, 'john', 70.200000000000003, 123, 'john')
以下是元組無效的,因為我們嘗試更新一個元組,這是不允許的。類似的操作在列表中是可以的:
#!/usr/bin/pythontuple = ( 'abcd', 786 , 2.23, 'john', 70.2 ) list = [ 'abcd', 786 , 2.23, 'john', 70.2 ] tuple[2] = 1000 # Invalid syntax with tuple list[2] = 1000 # Valid syntax with list
Python字典:
Python字典是一種哈希表型。他們像關聯數組或哈希在Perl中一樣,由鍵 - 值對組成。字典鍵幾乎可以是任何Python類型,但通常是數字或字符串。值可以是任意Python的對象。
字典是由花括號括號({}),可分配值,并用方括號([])訪問。例如:
#!/usr/bin/pythondict = {} dict['one'] = "This is one" dict[2] = "This is two"tinydict = {'name': 'john','code':6734, 'dept': 'sales'}print dict['one'] # Prints value for 'one' key print dict[2] # Prints value for 2 key print tinydict # Prints complete dictionary print tinydict.keys() # Prints all the keys print tinydict.values() # Prints all the values
這將產生以下結果:
This is one This is two {'dept': 'sales', 'code': 6734, 'name': 'john'} ['dept', 'code', 'name'] ['sales', 6734, 'john']
字典有元素順序的概念。它的元素是無序的。
數據類型轉換:
有時候,可能需要執行的內置類型之間的轉換。類型之間的轉換,只需使用類名作為函數。
有幾個內置的功能,從一種數據類型進行轉換為另一種。這些函數返回一個表示轉換值的新對象。
函數 | 描述 |
---|---|
int(x [,base]) | 將x轉換為一個整數。基數指定為base,如果x是一個字符串。 |
long(x [,base] ) | 將x轉換為一個長整數。基數指定為base,如果x是一個字符串。 |
float(x) | 將x轉換到一個浮點數。 |
complex(real [,imag]) | 創建一個復數。 |
str(x) | 轉換對象x為字符串表示形式。 |
repr(x) | 對象x轉換為一個表達式字符串。 |
eval(str) | 計算一個字符串,并返回一個對象。 |
tuple(s) | 把s轉換為一個元組。 |
list(s) | 把s轉換為一個列表。 |
set(s) | 把s轉換為一個集合。 |
dict(d) | 創建一個字典。 d必須的(鍵,值)元組序列。 |
frozenset(s) | 把s轉換為凍結集。 |
chr(x) | 整數轉換為一個字符。 |
unichr(x) | 整數轉換為一個Unicode字符。 |
ord(x) | 轉換單個字符為整數值。 |
hex(x) | 將整數轉換為十六進制字符串。 |
oct(x) | 將整數轉換為以八進制的字符串。 |
Python 3開發網絡爬蟲(一)
選擇Python版本
有2和3兩個版本, 3比較新, 聽說改動大. 根據我在知乎上搜集的觀點來看, 我還是傾向于使用”在趨勢中將會越來越火”的版本, 而非”目前已經很穩定而且很成熟”的版本. 這是個人喜好, 而且預測不一定準確. 但是如果Python3無法像Python2那么火, 那么整個Python語言就不可避免的隨著時間的推移越來越落后, 因此我想其實選哪個的最壞風險都一樣, 但是最好回報卻是Python3的大. 其實兩者區別也可以說大也可以說不大, 最終都不是什么大問題. 我選擇的是Python 3.
?
選擇參考資料
由于我是一邊學一邊寫, 而不是我完全學會了之后才開始很有條理的寫, 所以參考資料就很重要(本來應該是個人開發經驗很重要, 但我是零基礎).
- Python官方文檔
- 知乎相關資料(1)?這篇非常好, 通俗易懂的總覽整個Python學習框架.
- 知乎相關資料(2)
寫到這里的時候, 上面第二第三個鏈接的票數第一的回答已經看完了, 他們提到的有些部分(比如爬行的路線不能有回路)我就不寫了。
?
一個簡單的偽代碼
以下這個簡單的偽代碼用到了set和queue這兩種經典的數據結構, 集與隊列. 集的作用是記錄那些已經訪問過的頁面, 隊列的作用是進行廣度優先搜索.
queue Q set S StartPoint = "http://jecvay.com" Q.push(StartPoint) # 經典的BFS開頭 S.insert(StartPoint) # 訪問一個頁面之前先標記他為已訪問 while (Q.empty() == false) # BFS循環體T = Q.top() # 并且popfor point in PageUrl(T) # PageUrl(T)是指頁面T中所有url的集合, point是這個集合中的一個元素.if (point not in S)Q.push(point)S.insert(point)
這個偽代碼不能執行, ?我覺得我寫的有的不倫不類, 不類Python也不類C++.. 但是我相信看懂是沒問題的, 這就是個最簡單的BFS結構. 我是看了知乎里面的那個偽代碼之后, 自己用我的風格寫了一遍. 你也需要用你的風格寫一遍.
這里用到的Set其內部原理是采用了Hash表, 傳統的Hash對爬蟲來說占用空間太大, 因此有一種叫做Bloom Filter的數據結構更適合用在這里替代Hash版本的set. 我打算以后再看這個數據結構怎么使用, 現在先跳過, 因為對于零基礎的我來說, 這不是重點.
代碼實現(一): 用Python抓取指定頁面
我使用的編輯器是Idle, 安裝好Python3后這個編輯器也安裝好了, 小巧輕便, 按一個F5就能運行并顯示結果. 代碼如下:
#encoding:UTF-8 import urllib.requesturl = "http://www.baidu.com" data = urllib.request.urlopen(url).read() data = data.decode('UTF-8') print(data)
urllib.request是一個庫, 隸屬urllib.?點此打開官方相關文檔. 官方文檔應該怎么使用呢? 首先點剛剛提到的這個鏈接進去的頁面有urllib的幾個子庫, 我們暫時用到了request, 所以我們先看urllib.request部分. 首先看到的是一句話介紹這個庫是干什么用的:
The urllib.request module defines functions and classes which help in opening URLs (mostly HTTP) in a complex world — basic and digest authentication, redirections, cookies and more.
然后把我們代碼中用到的urlopen()函數部分閱讀完.
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False)
重點部分是返回值, 這個函數返回一個 http.client.HTTPResponse 對象, 這個對象又有各種方法, 比如我們用到的read()方法, 這些方法都可以根據官方文檔的鏈接鏈過去. 根據官方文檔所寫, 我用控制臺運行完畢上面這個程序后, 又繼續運行如下代碼, 以更熟悉這些亂七八糟的方法是干什么的.
>>> a = urllib.request.urlopen(full_url)
>>> type(a)
<class ‘http.client.HTTPResponse’>>>> a.geturl()
‘http://www.baidu.com/s?word=Jecvay’>>> a.info()
<http.client.HTTPMessage object at 0x03272250>>>> a.getcode()
200
代碼實現(二): 用Python簡單處理URL
如果要抓取百度上面搜索關鍵詞為Jecvay Notes的網頁, 則代碼如下
import urllib import urllib.requestdata={} data['word']='Jecvay Notes'url_values=urllib.parse.urlencode(data) url="http://www.baidu.com/s?" full_url=url+url_valuesdata=urllib.request.urlopen(full_url).read() data=data.decode('UTF-8') print(data)
data是一個字典, 然后通過urllib.parse.urlencode()來將data轉換為 ‘word=Jecvay+Notes’的字符串, 最后和url合并為full_url, 其余和上面那個最簡單的例子相同. 關于urlencode(), 同樣通過官方文檔學習一下他是干什么的. 通過查看
- urllib.parse.urlencode(query, doseq=False, safe=”, encoding=None, errors=None)
- urllib.parse.quote_plus(string, safe=”, encoding=None, errors=None)
Python 3開發網絡爬蟲(二)
上一回, 我學會了
- 用偽代碼寫出爬蟲的主要框架;
- 用Python的urllib.request庫抓取指定url的頁面;
- 用Python的urllib.parse庫對普通字符串轉符合url的字符串.
這一回, 開始用Python將偽代碼中的所有部分實現. 由于文章的標題就是”零基礎”, 因此會先把用到的兩種數據結構隊列和集合介紹一下. 而對于”正則表達式“部分, 限于篇幅不能介紹, 但給出我比較喜歡的幾個參考資料.
Python的隊列
在爬蟲程序中, 用到了廣度優先搜索(BFS)算法. 這個算法用到的數據結構就是隊列.
Python的List功能已經足夠完成隊列的功能, 可以用 append() 來向隊尾添加元素, 可以用類似數組的方式來獲取隊首元素, 可以用 pop(0) 來彈出隊首元素. 但是List用來完成隊列功能其實是低效率的, 因為List在隊首使用 pop(0) 和 insert() 都是效率比較低的,?Python官方建議使用collection.deque來高效的完成隊列任務.
from collections import deque queue = deque(["Eric", "John", "Michael"]) queue.append("Terry") # Terry 入隊 queue.append("Graham") # Graham 入隊 queue.popleft() # 隊首元素出隊 #輸出: 'Eric' queue.popleft() # 隊首元素出隊 #輸出: 'John' queue # 隊列中剩下的元素 #輸出: deque(['Michael', 'Terry', 'Graham'])
(以上例子引用自官方文檔)
Python的集合
在爬蟲程序中, 為了不重復爬那些已經爬過的網站, 我們需要把爬過的頁面的url放進集合中, 在每一次要爬某一個url之前, 先看看集合里面是否已經存在. 如果已經存在, 我們就跳過這個url; 如果不存在, 我們先把url放入集合中, 然后再去爬這個頁面.
Python提供了set這種數據結構. set是一種無序的, 不包含重復元素的結構. 一般用來測試是否已經包含了某元素, 或者用來對眾多元素們去重. 與數學中的集合論同樣, 他支持的運算有交, 并, 差, 對稱差.
創建一個set可以用 set() 函數或者花括號 {} . 但是創建一個空集是不能使用一個花括號的, 只能用 set() 函數. 因為一個空的花括號創建的是一個字典數據結構. 以下同樣是Python官網提供的示例.
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} >>> print(basket) # 這里演示的是去重功能 {'orange', 'banana', 'pear', 'apple'} >>> 'orange' in basket # 快速判斷元素是否在集合內 True >>> 'crabgrass' in basket False>>> # 下面展示兩個集合間的運算. ... >>> a = set('abracadabra') >>> b = set('alacazam') >>> a {'a', 'r', 'b', 'c', 'd'} >>> a - b # 集合a中包含元素 {'r', 'd', 'b'} >>> a | b # 集合a或b中包含的所有元素 {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'} >>> a & b # 集合a和b中都包含了的元素 {'a', 'c'} >>> a ^ b # 不同時包含于a和b的元素 {'r', 'd', 'b', 'm', 'z', 'l'
其實我們只是用到其中的快速判斷元素是否在集合內的功能, 以及集合的并運算.
Python的正則表達式
在爬蟲程序中, 爬回來的數據是一個字符串, 字符串的內容是頁面的html代碼. 我們要從字符串中, 提取出頁面提到過的所有url. 這就要求爬蟲程序要有簡單的字符串處理能力, 而正則表達式可以很輕松的完成這一任務.
參考資料
- 正則表達式30分鐘入門教程
- w3cschool 的Python正則表達式部分
- Python正則表達式指南
雖然正則表達式功能異常強大, 很多實際上用的規則也非常巧妙, 真正熟練正則表達式需要比較長的實踐鍛煉. 不過我們只需要掌握如何使用正則表達式在一個字符串中, 把所有的url都找出來, 就可以了. 如果實在想要跳過這一部分, 可以在網上找到很多現成的匹配url的表達式, 拿來用即可.
?
Python網絡爬蟲Ver 1.0 alpha
有了以上鋪墊, 終于可以開始寫真正的爬蟲了. 我選擇的入口地址是Fenng叔的Startup News, 我想Fenng叔剛剛拿到7000萬美金融資, 不會介意大家的爬蟲去光臨他家的小站吧. 這個爬蟲雖然可以勉強運行起來, 但是由于缺乏異常處理, 只能爬些靜態頁面, 也不會分辨什么是靜態什么是動態, 碰到什么情況應該跳過, 所以工作一會兒就要敗下陣來.
import re import urllib.request import urllibfrom collections import dequequeue = deque() visited = set()url = 'http://news.dbanotes.net' # 入口頁面, 可以換成別的queue.append(url) cnt = 0while queue:url = queue.popleft() # 隊首元素出隊visited |= {url} # 標記為已訪問print('已經抓取: ' + str(cnt) + ' 正在抓取 <--- ' + url)cnt += 1urlop = urllib.request.urlopen(url)if 'html' not in urlop.getheader('Content-Type'):continue# 避免程序異常中止, 用try..catch處理異常try:data = urlop.read().decode('utf-8')except:continue# 正則表達式提取頁面中所有隊列, 并判斷是否已經訪問過, 然后加入待爬隊列linkre = re.compile('href=\"(.+?)\"')for x in linkre.findall(data):if 'http' in x and x not in visited:queue.append(x)print('加入隊列 ---> ' + x)
這個版本的爬蟲使用的正則表達式是
'href=\"(.+?)\"'
所以會把那些.ico或者.jpg的鏈接都爬下來. 這樣read()了之后碰上decode(‘utf-8′)就要拋出異常. 因此我們用getheader()函數來獲取抓取到的文件類型, 是html再繼續分析其中的鏈接.
if 'html' not in urlop.getheader('Content-Type'):continue
但是即使是這樣, 依然有些網站運行decode()會異常. 因此我們把decode()函數用try..catch語句包圍住, 這樣他就不會導致程序中止. 程序運行效果圖如下:
爬蟲是可以工作了, 但是在碰到連不上的鏈接的時候, 它并不會超時跳過. 而且爬到的內容并沒有進行處理, 沒有獲取對我們有價值的信息, 也沒有保存到本地. 下次我們可以完善這個alpha版本.
Python3網絡爬蟲(三): 偽裝瀏覽器
上一次我自學爬蟲的時候, 寫了一個簡陋的勉強能運行的爬蟲alpha. alpha版有很多問題. 比如一個網站上不了, 爬蟲卻一直在等待連接返回response, 不知道超時跳過; 或者有的網站專門攔截爬蟲程序, 我們的爬蟲也不會偽裝自己成為瀏覽器正規部隊; 并且抓取的內容沒有保存到本地, 沒有什么作用. 這次我們一個個解決這些小問題.
此外, 在我寫這系列文章的第二篇的時候, 我還是一個對http的get和post以及response這些名詞一無所知的人, 但是我覺得這樣是寫不好爬蟲的. 于是我參考了 <<計算機網絡–自頂向下方法>> 這本書的第二章的大部分內容. 如果你也一樣對http的機制一無所知, 我也推薦你找一找這方面的資料來看. 在看的過程中, 安裝一個叫做Fiddler的軟件, 邊學邊實踐, 觀察瀏覽器是如何訪問一個網站的, 如何發出請求, 如何處理響應, 如何進行跳轉, 甚至如何通過登錄認證. 有句老話說得好, 越會用Fiddler, 就對理論理解更深刻; 越對理論理解深刻, Fiddler就用得越順手. 最后我們在用爬蟲去做各種各樣的事情的時候, Fiddler總是最得力的助手之一.
添加超時跳過功能
首先, 我簡單地將
urlop = urllib.request.urlopen(url)
改為
urlop = urllib.request.urlopen(url, timeout = 2)
運行后發現, 當發生超時, 程序因為exception中斷. 于是我把這一句也放在try .. except 結構里, 問題解決.
?
支持自動跳轉
在爬 http://baidu.com 的時候, 爬回來一個沒有什么內容的東西, 這個東西告訴我們應該跳轉到 http://www.baidu.com . 但是我們的爬蟲并不支持自動跳轉, 現在我們來加上這個功能, 讓爬蟲在爬 baidu.com 的時候能夠抓取 www.baidu.com 的內容.
首先我們要知道爬 http://baidu.com 的時候他返回的頁面是怎么樣的, 這個我們既可以用 Fiddler 看, 也可以寫一個小爬蟲來抓取. 這里我抓到的內容如下, 你也應該嘗試一下寫幾行 python 來抓一抓.
<html>
<meta http-equiv=”refresh” content=”0;url=http://www.baidu.com/”>
</html>
看代碼我們知道這是一個利用 html 的 meta 來刷新與重定向的代碼, 其中的0是等待0秒后跳轉, 也就是立即跳轉. 這樣我們再像上一次說的那樣用一個正則表達式把這個url提取出來就可以爬到正確的地方去了. 其實我們上一次寫的爬蟲已經可以具有這個功能, 這里只是單獨拿出來說明一下 http 的 meta 跳轉.
偽裝瀏覽器正規軍
前面幾個小內容都寫的比較少. 現在詳細研究一下如何讓網站們把我們的Python爬蟲當成正規的瀏覽器來訪. 因為如果不這么偽裝自己, 有的網站就爬不回來了. 如果看過理論方面的知識, 就知道我們是要在 GET 的時候將 User-Agent 添加到header里.
如果沒有看過理論知識, 按照以下關鍵字搜索學習吧 :D
- HTTP 報文分兩種:?請求報文和響應報文
- 請求報文的請求行與首部行
- GET,?POST, HEAD, PUT, DELETE 方法
我用 IE 瀏覽器訪問百度首頁的時候, 瀏覽器發出去的請求報文如下:
GET http://www.baidu.com/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: www.baidu.com
DNT: 1
Connection: Keep-Alive
Cookie: BAIDUID=57F4D171573A6B88A68789EF5DDFE87:FG=1; uc_login_unique=ccba6e8d978872d57c7654130e714abd; BD_UPN=11263145; BD
然后百度收到這個消息后, 返回給我的的響應報文如下(有刪節):
HTTP/1.1 200 OK
Date: Mon, 29 Sep 2014 13:07:01 GMT
Content-Type: text/html; charset=utf-8
Connection: Keep-Alive
Vary: Accept-Encoding
Cache-Control: private
Cxy_all: baidu+8b13ba5a7289a37fb380e0324ad688e7
Expires: Mon, 29 Sep 2014 13:06:21 GMT
X-Powered-By: HPHP
Server: BWS/1.1
BDPAGETYPE: 1
BDQID: 0x8d15bb610001fe79
BDUSERID: 0
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=0; path=/
Content-Length: 80137<!DOCTYPE html><!–STATUS OK–><html><head><meta http-equiv=”content-type” content=”text/html;charset=utf-8″><meta http-equiv=”X-UA-Compatible” content=”IE=Edge”><link rel=”dns-prefetch” href=”//s1.bdstatic.com”/><link rel=”dns-prefetch” href=”//t1.baidu.com”/><link rel=”dns-prefetch” href=”//t2.baidu.com”/><link rel=”dns-prefetch” href=”//t3.baidu.com”/><link rel=”dns-prefetch” href=”//t10.baidu.com”/><link rel=”dns-prefetch” href=”//t11.baidu.com”/><link rel=”dns-prefetch” href=”//t12.baidu.com”/><link rel=”dns-prefetch” href=”//b1.bdstatic.com”/><title>百度一下,你就知道</title><style index=”index” > ……….這里省略兩萬字…………….?</script></body></html>
如果能夠看懂這段話的第一句就OK了, 別的可以以后再配合 Fiddler 慢慢研究. 所以我們要做的就是在 Python 爬蟲向百度發起請求的時候, 順便在請求里面寫上 User-Agent, 表明自己是瀏覽器君.
在 GET 的時候添加 header 有很多方法, 下面介紹兩種方法.
第一種方法比較簡便直接, 但是不好擴展功能, 代碼如下:
import urllib.requesturl = 'http://www.baidu.com/' req = urllib.request.Request(url, headers = {'Connection': 'Keep-Alive','Accept': 'text/html, application/xhtml+xml, */*','Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3','User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' }) oper = urllib.request.urlopen(req) data = oper.read() print(data.decode())
?
第二種方法使用了 build_opener 這個方法, 用來自定義 opener, 這種方法的好處是可以方便的拓展功能, 例如下面的代碼就拓展了自動處理?Cookies 的功能.
import urllib.request import http.cookiejar# head: dict of header def makeMyOpener(head = {'Connection': 'Keep-Alive','Accept': 'text/html, application/xhtml+xml, */*','Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3','User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' }):cj = http.cookiejar.CookieJar()opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))header = []for key, value in head.items():elem = (key, value)header.append(elem)opener.addheaders = headerreturn openeroper = makeMyOpener() uop = oper.open('http://www.baidu.com/', timeout = 1000) data = uop.read() print(data.decode())
上述代碼運行后通過 Fiddler 抓到的 GET 報文如下所示:
GET http://www.baidu.com/ HTTP/1.1
Accept-Encoding: identity
Connection: close
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3
可見我們在代碼里寫的東西都添加到請求報文里面了.
保存抓回來的報文
順便說說文件操作. Python 的文件操作還是相當方便的. 我們可以講抓回來的數據 data 以二進制形式保存, 也可以經過 decode() 處理成為字符串后以文本形式保存. 改動一下打開文件的方式就能用不同的姿勢保存文件了. 下面是參考代碼:
def saveFile(data):save_path = 'D:\\temp.out'f_obj = open(save_path, 'wb') # wb 表示打開方式f_obj.write(data)f_obj.close()# 這里省略爬蟲代碼 # ...# 爬到的數據放到 dat 變量里 # 將 dat 變量保存到 D 盤下 saveFile(dat)
?
下回我們會用 Python 來爬那些需要登錄之后才能看到的信息. 在那之前, 我已經對 Fiddler 稍微熟悉了. 希望一起學習的也提前安裝個?Fiddler 玩一下.Python3網絡爬蟲(四): 登錄
今天的工作很有意思, 我們用 Python 來登錄網站, 用Cookies記錄登錄信息, 然后就可以抓取登錄之后才能看到的信息. 今天我們拿知乎網來做示范. 為什么是知乎? 這個很難解釋, 但是肯定的是知乎這么大這么成功的網站完全不用我來幫他打廣告. 知乎網的登錄比較簡單, 傳輸的時候沒有對用戶名和密碼加密, 卻又不失代表性, 有一個必須從主頁跳轉登錄的過程.
不得不說一下, Fiddler 這個軟件是?Tpircsboy?告訴我的. 感謝他給我帶來這么好玩的東西.
第一步: 使用 Fiddler 觀察瀏覽器行為
在開著 Fiddler 的條件下運行瀏覽器, 輸入知乎網的網址 http://www.zhihu.com 回車后到 Fiddler 中就能看到捕捉到的連接信息. 在左邊選中一條 200 連接, 在右邊打開 Inspactors 透視圖, 上方是該條連接的請求報文信息, 下方是響應報文信息.
其中 Raw 標簽是顯示報文的原文. 下方的響應報文很有可能是沒有經過解壓或者解碼的, 這種情況他會在中間部位有一個小提示, 點擊一下就能解碼顯示出原文了.
?
以上這個截圖是在未登錄的時候進入 http://www.zhihu.com 得到的. 現在我們來輸入用戶名和密碼登陸知乎網, 再看看瀏覽器和知乎服務器之間發生了什么.
?
點擊登陸后, 回到 Fiddler 里查看新出現的一個 200 鏈接. 我們瀏覽器攜帶者我的帳號密碼給知乎服務器發送了一個 POST, 內容如下:
POST http://www.zhihu.com/login HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
X-Requested-With: XMLHttpRequest
Referer: http://www.zhihu.com/#signin
Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 6.4; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Length: 97
DNT: 1
Host: www.zhihu.com
Connection: Keep-Alive
Pragma: no-cache
Cookie: __utma=51854390.1539896551.1412320246.1412320246.1412320246.1; __utmb=51854390.6.10.1412320246; __utmc=51854390; __utmz=51854390.1412320246.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmv=51854390.000–|3=entry_date=20141003=1_xsrf=4b41f6c7a9668187ccd8a610065b9718&email=此處涂黑%40gmail.com&password=此處不可見&rememberme=y
截圖如下:
我的瀏覽器給 http://www.zhihu.com/login 這個網址(多了一個/login) 發送了一個POST, 內容都已經在上面列出來了, 有用戶名, 有密碼, 有一個”記住我”的 yes, 其中這個 WebForms 標簽下 Fiddler 能夠比較井井有條的列出來 POST 的內容. 所以我們用 Python 也發送相同的內容就能登錄了. 但是這里出現了一個 Name 為 _xsrf 的項, 他的值是?4b41f6c7a9668187ccd8a610065b9718. 我們要先獲取這個值, 然后才能給他發.
瀏覽器是如何獲取的呢, 我們剛剛是先訪問了 http://www.zhihu.com/ 這個網址, 就是首頁, 然后登錄的時候他卻給 http://www.zhihu.com/login 這個網址發信息. 所以用偵探一般的思維去思考這個問題, 就會發現肯定是首頁把 _xsrf 生成發送給我們, 然后我們再把這個 _xsrf 發送給 /login 這個 url. 這樣一會兒過后我們就要從第一個 GET 得到的響應報文里面去尋找 _xsrf
截圖下方的方框說明, 我們不僅登錄成功了, 而且服務器還告訴我們的瀏覽器如何保存它給出的 Cookies 信息. 所以我們也要用 Python 把這些 Cookies 信息記錄下來.
這樣 Fiddler 的工作就基本結束了!
第二步: 解壓縮
簡單的寫一個 GET 程序, 把知乎首頁 GET 下來, 然后 decode() 一下解碼, 結果報錯. 仔細一看, 發現知乎網傳給我們的是經過 gzip 壓縮之后的數據. 這樣我們就需要先對數據解壓. Python 進行 gzip 解壓很方便, 因為內置有庫可以用. 代碼片段如下:
import gzip def ungzip(data):try: # 嘗試解壓print('正在解壓.....')data = gzip.decompress(data)print('解壓完畢!')except:print('未經壓縮, 無需解壓')return data
通過 opener.read() 讀取回來的數據, 經過 ungzip 自動處理后, 再來一遍 decode() 就可以得到解碼后的 str 了
第二步: 使用正則表達式獲取沙漠之舟
_xsrf 這個鍵的值在茫茫無際的互聯網沙漠之中指引我們用正確的姿勢來登錄知乎, 所以 _xsrf 可謂沙漠之舟. 如果沒有 _xsrf, 我們或許有用戶名和密碼也無法登錄知乎(我沒試過, 不過我們學校的教務系統確實如此) 如上文所說, 我們在第一遍 GET 的時候可以從響應報文中的 HTML 代碼里面得到這個沙漠之舟. 如下函數實現了這個功能, 返回的 str 就是 _xsrf 的值.
import re def getXSRF(data):cer = re.compile('name=\"_xsrf\" value=\"(.*)\"', flags = 0)strlist = cer.findall(data)return strlist[0]
?
第三步: 發射 POST !!
集齊 _xsrf, id, password 三大法寶, 我們可以發射 POST 了. 這個 POST 一旦發射過去, 我們就登陸上了服務器, 服務器就會發給我們 Cookies. 本來處理 Cookies 是個麻煩的事情, 不過 Python 的 http.cookiejar 庫給了我們很方便的解決方案, 只要在創建 opener 的時候將一個 HTTPCookieProcessor 放進去, Cookies 的事情就不用我們管了. 下面的代碼體現了這一點.
import http.cookiejar import urllib.request def getOpener(head):# deal with the Cookiescj = http.cookiejar.CookieJar()pro = urllib.request.HTTPCookieProcessor(cj)opener = urllib.request.build_opener(pro)header = []for key, value in head.items():elem = (key, value)header.append(elem)opener.addheaders = headerreturn opener
getOpener 函數接收一個 head 參數, 這個參數是一個字典. 函數把字典轉換成元組集合, 放進 opener. 這樣我們建立的這個 opener 就有兩大功能:
- 自動處理使用 opener 過程中遇到的 Cookies
- 自動在發出的 GET 或者 POST 請求中加上自定義的 Header
第四部: 正式運行
正式運行還差一點點, 我們要把要 POST 的數據弄成 opener.open() 支持的格式. 所以還要 ?urllib.parse 庫里的 urlencode() 函數. 這個函數可以把 字典 或者 元組集合 類型的數據轉換成 & 連接的 str.
str 還不行, 還要通過 encode() 來編碼, 才能當作 opener.open() 或者 urlopen() 的 POST 數據參數來使用. 代碼如下:
url = 'http://www.zhihu.com/' opener = getOpener(header) op = opener.open(url) data = op.read() data = ungzip(data) # 解壓 _xsrf = getXSRF(data.decode())url += 'login' id = '這里填你的知乎帳號' password = '這里填你的知乎密碼' postDict = {'_xsrf':_xsrf,'email': id,'password': password,'rememberme': 'y' } postData = urllib.parse.urlencode(postDict).encode() op = opener.open(url, postData) data = op.read() data = ungzip(data)print(data.decode()) # 你可以根據你的喜歡來處理抓取回來的數據了!
代碼運行后, 我們發現自己關注的人的動態(顯示在登陸后的知乎首頁的那些), 都被抓取回來了. 下一步做一個統計分析器, 或者自動推送器, 或者內容分級自動分類器, 都可以.
完整代碼如下:
import gzip import re import http.cookiejar import urllib.request import urllib.parsedef ungzip(data):try: # 嘗試解壓print('正在解壓.....')data = gzip.decompress(data)print('解壓完畢!')except:print('未經壓縮, 無需解壓')return datadef getXSRF(data):cer = re.compile('name=\"_xsrf\" value=\"(.*)\"', flags = 0)strlist = cer.findall(data)return strlist[0]def getOpener(head):# deal with the Cookiescj = http.cookiejar.CookieJar()pro = urllib.request.HTTPCookieProcessor(cj)opener = urllib.request.build_opener(pro)header = []for key, value in head.items():elem = (key, value)header.append(elem)opener.addheaders = headerreturn openerheader = {'Connection': 'Keep-Alive','Accept': 'text/html, application/xhtml+xml, */*','Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3','User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko','Accept-Encoding': 'gzip, deflate','Host': 'www.zhihu.com','DNT': '1' }url = 'http://www.zhihu.com/' opener = getOpener(header) op = opener.open(url) data = op.read() data = ungzip(data) # 解壓 _xsrf = getXSRF(data.decode())url += 'login' id = '這里填你的知乎帳號' password = '這里填你的知乎密碼' postDict = {'_xsrf':_xsrf,'email': id,'password': password,'rememberme': 'y' } postData = urllib.parse.urlencode(postDict).encode() op = opener.open(url, postData) data = op.read() data = ungzip(data)print(data.decode())