Python:基礎&爬蟲

yiwanbin發表於2023-10-29

Python:基礎&爬蟲

Python爬蟲學習(網路爬蟲(又稱為網頁蜘蛛,網路機器人,在FOAF社群中間,更經常的稱為網頁追逐者),是一種按照一定的規則,自動地抓取全球資訊網資訊的程式或者指令碼。另外一些不常使用的名字還有螞蟻、自動索引模擬程式或者蠕蟲。)

一、基礎知識準備

Python在各個程式語言中比較適合新手學習,Python直譯器易於擴充套件,可以使用CC++或其他可以透過C呼叫的語言擴充套件新的功能和資料型別。 [4] Python也可用於可定製化軟體中的擴充套件程式語言。Python豐富的標準庫,提供了適用於各個主要系統平臺的原始碼機器碼

1 條件判斷語句

score = 60
if score >=90 and score <=100:
    print("本次考試等級為A")
elif score >=70 and score <90:  #elif == else if
    print("本次考試等級為B")      
else:
    print("本次考試等級為C")      #output: 本次考試等級為C

2 迴圈語句

2.1 for迴圈

for i in range(5): #輸出5個數 [0 1 2 3 4]
    print(i)

for i in range(0,10,3):#從[0,10),以3為間距輸出                #output: [0 3 6 9]
    print(i)


for i in range(-10,-100,-30):#從[-10,-100),以-30為間距     #output: [-10 -40 -70]
    print(i)



name="chengdu" 
for x in name:
    print(x)            #output: [c h e n g d u]

    
a = ["a", "b", "c"] 
for i in range(len(a)):
     print(i, a[i])    #output: [0 a 1 b 2 c]

2.2 while迴圈

i=0
while i<3:
    print("這是第%d次迴圈:"%(i+1))
    print("i=%d"%i)
    i+=1

'''#output: 
這是第1次迴圈:
i=0
這是第2次迴圈:
i=1
這是第3次迴圈:
i=2
'''
count = 0
while count<3:
    print(count,"小於3")
    count +=1
else:
    print(count,"大於或等於3")
    
'''#output: 
0 小於3
1 小於3
2 小於3
3 大於或等於3
'''

3 字串

str="chengdu"			  
print(str)                #chengdu
print(str[0])             #c
print(str[0:5])           #[0,5) cheng
print(str[1:7:2])         #[起始位置:結束位置:步進值] hnd
print(str[5:])            #顯示5以後的 du
print(str[:5])            #顯示5以前的 cheng
print(str+",hello")       #字串連線 chengdu,hello
print(str*3)              #列印3次 chengduchengduchengdu
print("hello\nchengdu")   #\n換行 hello  chengdu
print(r"hello\nchengdu")  #前面加"r",表示顯示原始字串,不進行轉義hello\nchengdu
print("-"*30)             #列印30個“-”

4 列表-List

列表中的每個元素都分配一個數字 - 它的位置或索引,第一個索引是0,第二個索引是1,依此類推。

4.1 列表定義

namelist = ["小張","小王","小李"]
testlist = [1,"測試","str"]  #列表中可以儲存混合型別
testlist = [["a","b"],["c","d"],["e","f","g"]] #列表巢狀

4.2 列表元素輸出

namelist = ["小張","小王","小李"]
#輸出指定元素
print(namelist[1])     #output: 小王

#遍歷輸出
for name in namelist: 
    print(name)
   
'''output
小張
小王
小李
'''    

#使用列舉函式enenumerate(),同時拿到列表下標和元素類容
for i,name in enumerate(namelist): 
    print(i,name)
    
'''output
0 小張
1 小王
2 小李
'''

4.3 列表元素切片

如下所示:L=[‘Google’, ‘Python’, ‘Taobao’]

Python 表示式 結果 描述
L[2] ‘Taobao’ 讀取第三個元素
L[-1] ‘Taobao’ 讀取最後一個元素
L[1:] [‘Python’, ‘Taobao’] 輸出從第二個元素開始後的所有元素
L[:-1] [‘Google’, ‘Python’] 輸出從第一個到倒數第一個的所有元素
L[-2:] [‘Python’, ‘Taobao’] 輸出從倒數第二個到末尾的所有元素

4.4 列表元素追加

#append
a = [1,2]
b = [3,4]
a.append(b)  #將b列表當做一個元素加入到a中
print(a)     #output: [1, 2, [3, 4]]

#extend
a = [1,2]
b = [3,4]
a.extend(b) #將b列表中的誒個元素,逐一追加到a中
print(a)    #output: [1, 2, 3, 4]

#insert
a=[1,2,4]
a.insert(2,3) ##在下標為2的位置插入3   #指定下標位置插入元素(第一個表示下標,第二個表示元素)
print(a)  #output: [1, 2, 3, 4] 

4.5 列表元素刪除

#del
a = ["小張","小王","小李"]
del a[2]                      #刪除指定下標元素
print(a)                      #output: ['小張', '小王']

#pop
a = ["小張","小王","小李"]
a.pop()                      #彈出末尾元素
print(a)                     #output: ['小張', '小王']

#remove
a = ["小張","小王","小李"]
a.remove("小李")             #直接刪除指定內容的元素
print(a)                    #output: ['小張', '小李']

4.6 列表元素修改

a = ["小張","小王","小李"]
a[2] = "小紅"				 #修改指定下標元素內容
print(a) 				  #output: ['小張', '小王', '小紅']

4.7 列表元素查詢

#in / not in
a = ["小張","小王","小李"]
findName = input("請輸入你要查詢的學生姓名:")
if findName in a:
    print("找到")
else:
    print("未找到")
    
#index
a = ["小張","小王","小李"]
print(a.index("小王",0,2))    #可以查詢指定下標範圍的元素,並返回找到對應資料的下標  #output: 1
print(a.index("小李",0,2))   #範圍區間:左開右閉[0,2) # ValueError: '小李' is not in list

#count
print(a.count("小王"))      #查詢某個元素出現的次數  #output: 1

4.8 列表元素反轉和排序

a = [1,4,2,3]
a.reverse()             #將列表所有元素反轉
print(a)                #output: [3, 2, 4, 1]
 
a.sort()                #升序
print(a)                #output: [1, 2, 3, 4]

a.sort(reverse=True)    #降序
print(a)                #output: [1, 2, 3, 4]

5 前段知識綜合練習

Topic: 將8個老師隨機分配到3個辦公室

import random
offices = [[],[],[]]							#3個教室
teachers = ["A","B","C","D","E","F","G","H"]	#8個老師
for teacher in teachers:                 		#遍歷teachers放入office中
    index = random.randint(0,2)   		 		#產生隨機數0,1,2
    offices[index].append(teacher)     		    #將teachers追加到office中
i=1 #office1
for office in offices:                			 #輸出每個office人數和對應的老師
    print("office%d的人數為:%d"%(i,len(office)))
    i += 1  #遍歷offices
    for name in office:
        print("%s"%name,end="\t")    			 #列印每個office老師的名字
    print("\n")                       			 #列印完一個office換行
    print("-"*20)                    			 #列印完一個office輸出20個-

6 元組-Tuple

元組與列表類似,不同之處在於元組的元素不能修改。
元組使用小括號,列表使用方括號。

6.1 元組定義

tup1=()     #空元組
tup2=(5)    #<class 'int'>  不是元組
tup2=(5,)   #<class 'tuple'>
tup3 = ('Google', 'Python', 1997, 2000)

6.2 元組元素切片

tup=(1,2,3)
print(tup[0])    #第一個元素     #output:  1
print(tup[-1])   #最後一個元素   #output:  3
print(tup[0:2])  #左閉右開[0,2) #output:  (1, 2)

6.3 元組元素增加(連線)

tup1 = (12,34,56)
tup2 = ("ab","cd","ef")
tup3 = tup1+tup2
print(tup3)          #(12, 34, 56, 'ab', 'cd', 'ef')

6.4 元組元素刪除

tup1 = (12,34,56)
#del tup1[0]    #不允許刪除單個元素
del tup1        #刪除了整個元組變數

6.5 元組元素不能修改

tup1 = (12,34,56)
tup1[0] = 72  #報錯 不能修改

7 字典-dict

字典使用鍵值對(key=>value)儲存;鍵必須是唯一的,但值則不必。

7.1 字典定義

dict = {key1 : value1, key2 : value2 }
info = {"name":"簡簡","age":18}

7.2 字典訪問

info = {"name":"簡簡","age":18}
print(info["name"])
print(info["age"])

#訪問不存在鍵
print(info["sex"])               #直接訪問不存在的鍵,會報錯
print(info.get("sex"))           #使用get()方法,訪問不存在的鍵,預設返回:none
print(info.get("sex","沒有"))     #沒有找到的時候,返回自定義值  #output: 沒有

7.3 字典鍵值增加

info = {"name":"簡簡","age":18}
info["sex"]="man"   			#新增sex
print(info)         			#output: {'name': '簡簡', 'age': 18, 'sex': 'man'}

7.4 字典鍵值刪除

#del
info = {"name":"簡簡","age":18}
del info["name"]                #刪除name鍵值對
print(info)                     #output: {'age': 18}

del info                        #刪除整個字典
print(info)                     #output: NameError: name 'info' is not defined

#clear
info = {"name":"簡簡","age":18}
info.clear()                     #清空字典內鍵值對
print(info)                      #output: {}

7.5 字典鍵值修改

info = {"name":"簡簡","age":18}
info["age"]=20
print(info)

7.6 字典鍵值查詢

info = {"name":"簡簡","age":18}
print(info.keys())               #得到所有的鍵     #output: dict_keys(['name', 'age'])
print(info.values())             #得到所有的值     #output: dict_values(['簡簡', 18])
print(info.items())     		 #得到所有的鍵值對 #output: dict_items([('name', '簡簡'), ('age', 18)])


#遍歷所有的鍵
for key in info.keys():
    print(key)     #output: name age
    
#遍歷所有的值
for value in info.values():
    print(value)     #output: 簡簡 18
    
#遍歷所有的鍵值對
for key,value in info.items():
        print("(key=%s,value=%s)"%(key,value)) 
#output: (key=name,value=簡簡) (key=age,value=18)

8 函式

8.1 函式定義和使用

def printinfo(a,b): #函式定義
    c =a + b
    print(c)

printinfo(1,2) 		#函式的使用

8.2 帶返回值的函式

def info(a,b):
    c =a + b
    return c 		#返回值

print(info(1,2)) 

8.3 返回多個值的函式

def divid(a,b):
    shang = a//b
    yushu = a%b
    return shang,yushu #多個返回值用逗號隔開

sh,yu = divid(5,2)     #需要用多個值來儲存返回內容
print("商:%d 餘數:%d"%(sh,yu))

9 檔案操作

9.1 開啟檔案(open)

用法:物件=open(檔名,訪問模式)

f = open('test.txt', 'w')
模式 說明
r 以只讀方式開啟檔案。檔案的指標將會放在檔案的開頭。這是預設模式。
w 開啟一個檔案只用於寫入。如果該檔案已存在則將其覆蓋。如果該檔案不存在,建立新檔案。
a 開啟一個檔案用於追加。如果該檔案已存在,檔案指標將會放在檔案的結尾。也就是說,新的內容將會被寫入到已有內容之後。如果該檔案不存在,建立新檔案進行寫入。
rb 以二進位制格式開啟一個檔案用於只讀。檔案指標將會放在檔案的開頭。這是預設模式。
wb 以二進位制格式開啟一個檔案只用於寫入。如果該檔案已存在則將其覆蓋。如果該檔案不存在,建立新檔案。
ab 以二進位制格式開啟一個檔案用於追加。如果該檔案已存在,檔案指標將會放在檔案的結尾。也就是說,新的內容將會被寫入到已有內容之後。如果該檔案不存在,建立新檔案進行寫入。
r+ 開啟一個檔案用於讀寫。檔案指標將會放在檔案的開頭。
w+ 開啟一個檔案用於讀寫。如果該檔案已存在則將其覆蓋。如果該檔案不存在,建立新檔案。
a+ 開啟一個檔案用於讀寫。如果該檔案已存在,檔案指標將會放在檔案的結尾。檔案開啟時會是追加模式。如果該檔案不存在,建立新檔案用於讀寫。
rb+ 以二進位制格式開啟一個檔案用於讀寫。檔案指標將會放在檔案的開頭。
wb+ 以二進位制格式開啟一個檔案用於讀寫。如果該檔案已存在則將其覆蓋。如果該檔案不存在,建立新檔案。
ab+ 以二進位制格式開啟一個檔案用於追加。如果該檔案已存在,檔案指標將會放在檔案的結尾。如果該檔案不存在,建立新檔案用於讀寫。

9.2 關閉檔案(close)

用法:物件.close()

f.close()

9.3 寫資料(write)

用法:物件.write()

f=open("test.txt","w")  # 開啟檔案,w(寫模式)-檔案不存在就在當前路徑給你新建一個
f.write("hello,world")  # write將字元寫入檔案
f.close()

9.4 讀資料(read)

用法:物件.read()

f=open("test.txt","r")   #開啟檔案,r(讀模式)
content=f.read(5)        #read讀取5個字元
print(content)
f.close()

9.5 讀一行資料(readline)

用法:物件.readline()

f = open('test.txt', 'r')
content = f.readline()
print("1:%s"%content)#讀取一行
content = f.readline()
print("2:%s"%content)#再讀下一行
f.close()

9.6 讀多行資料(readlines)

用法:物件.readlines()

f=open("test.txt","r")   #開啟檔案,r(讀模式)
content=f.readlines()    #readlines讀取整個檔案,以列表形式輸出
print(content)           #輸出形式為列表 #output: ['hello,world\n', 'hello,world']

#對列表進行處理,按序號一行一行輸出
i=1
for temp in content:
    print("%d:%s" % (i, temp))
    i += 1  #output: 1:hello,world 2:hello,world
f.close() 

9.7 OS模組

  • 使用該模組必須先匯入模組:
    import os
os模組中的函式:
序號 函式名稱 描述 格式
1 getcwd() 獲取當前的工作目錄 格式:os.getcwd() 返回值:路徑字串
2 chdir() 修改當前工作目錄 格式:os.chdir() 返回值:None
3 listdir() 獲取指定資料夾中的 所有檔案和資料夾組成的列表 格式:os.listdir(目錄路徑) 返回值:目錄中內容名稱的列表
4 mkdir() 建立一個目錄/資料夾 格式:os.mkdir(目錄路徑) 返回值:None
5 makedirs() 遞迴建立資料夾 格式:os.makedirs(路徑)
6 rmdir() 移除一個目錄(必須是空目錄) 格式:os.rmdir(目錄路徑) 返回值:None
7 removedirs() 遞迴刪除資料夾 格式:os.removedirs(目錄路徑) 返回值:None 注意最底層目錄必須為空
8 rename() 修改檔案和資料夾的名稱 格式:os.rename(原始檔或資料夾,目標檔案或資料夾) 返回值:None
9 stat() 獲取檔案的相關 資訊 格式:os.stat(檔案路徑) 返回值:包含檔案資訊的元組
10 system() 執行系統命令 格式:os.system() 返回值:整型 慎用! 玩意來個rm -rf 你就爽了!
11 getenv() 獲取系統環境變數 格式:os.getenv(獲取的環境變數名稱) 返回值:字串
12 putenv() 設定系統環境變數 格式:os.putenv(‘環境變數名稱’,值) 返回值:無 注意:無法正常的getenv檢測到。
13 exit() 推出當前執行命令,直接關閉當前操作 格式:exit() 返回值:無

10 異常處理

10.1 異常簡介

 print '-----test--1---'  
    open('123.txt','r')  
 print '-----test--2---'

image-20200323100011079

開啟一個不存在的檔案123.txt,當找不到123.txt 檔案時,就會丟擲給我們一個IOError型別的錯誤,No such file or directory:123.txt (沒有123.txt這樣的檔案或目錄)

10.2 捕獲異常

try:
     print('-----test--1---')  
    open('123.txt','r') 	 	 
    print('-----test--2---')
except IOError:
    pass

此程式看不到任何錯誤,因為用except 捕獲到了IOError異常,並新增了處理的方法
pass 表示實現了相應的實現,但什麼也不做;如果把pass改為print語句,那麼就會輸出其他資訊

總結:

image-20200323100335376

把可能出現問題的程式碼,放在try中
把處理異常的程式碼,放在except中

try:
  print num 
except IOError:
  print('產生錯誤了')

上例程式,已經使用except來捕獲異常,但是還會看到錯誤的資訊提示

except捕獲的錯誤型別是IOError,而此時程式產生的異常為 NameError ,所以except沒有生效

try:
  print num
except NameError:
  print('產生錯誤了')

Python的一些內建異常:

異常 描述
Exception 常規錯誤的基類
AttributeError 物件沒有這個屬性
IOError 輸入/輸出操作失敗
IndexError 序列中沒有此索引(index)
KeyError 對映中沒有這個鍵
NameError 未宣告/初始化物件 (沒有屬性)
SyntaxError Python 語法錯誤
TypeError 對型別無效的操作
ValueError 傳入無效的引數
ZeroDivisionError 除(或取模)零 (所有資料型別)
更多可以參考:http://blog.csdn.net/gavin_john/article/details/50738323

10.3 捕獲多個異常

#coding=utf-8 
try:
 print('-----test--1---')
 open('123.txt','r') # 如果123.txt檔案不存在,那麼會產生 IOError 異常  
 print('-----test--2---')
 print(num)# 如果num變數沒有定義,那麼會產生 NameError 異常
 except (IOError,NameError):
 #如果想透過一次except捕獲到多個異常可以用一個元組的形式

10.4 獲取異常的資訊描述

image-20200323102016442

image-20200323102016442

image-20200323102023020.png

image-20200323102023020.png

10.5 try…finally…

在程式中,如果一個段程式碼必須要執行,即無論異常是否產生都要執行,那麼此時就需要使用finally。 比如檔案關閉,釋放鎖,把資料庫連線返還給連線池等

import time
try:
    f = open('test.txt')
    try:
        while True:
            content = f.readline()
            if len(content) == 0:
                break
            time.sleep(2)
            print(content)
    except:
         #如果在讀取檔案的過程中,產生了異常,那麼就會捕獲到  
         #比如 按下了 ctrl+c
        pass
    finally:
        f.close()
        print('關閉檔案')
except:
    print("沒有這個檔案")

test.txt檔案中每一行資料列印,但是我有意在每列印一行之前用time.sleep方法暫停2秒鐘。這樣做的原因是讓程式執行得慢一些。在程式執行的時候,按Ctrl+c中斷(取消)程式。
我們可以觀察到KeyboardInterrupt異常被觸發,程式退出。但是在程式退出之前,finally從句仍然被執行,把檔案關閉。

11 Python知識

除法

  • 除 /
  • 整除 //
  • 求餘 %
  • 商和餘數的元組 divmod

移位操作

左移(<<)

`a<,左移 n 位相當於原運算元乘以 2^n,原運算元不發生變化。

>>> 3<<1      # 向右移動一位,相當於是乘以2
6             
>>> -3<<2     # 向右移動一位,相當於是乘以4
-12
右移(>>)

a>>n,則a' =a//(2^n),左移 n 位相當於原運算元整除 2^n,原運算元不發生變化。

>>> 2>>1     # 移動一位,相當於是2//2
1            
>>> 2>>2     # 相當於先左移一位得到1,結果1再除以2等於0
0
>>> 2>>3     # 相當於2//8
0  
          
>>> -8>>2    # 移動2位,相當於-8//4
-2
>>> -8>>3    # 移動3位,相當於是用結果-2再除以2
-1
>>> -8>>4    # 移動4位,相當於是用結果-1再除以2
-1

如果運算元是正數,那麼對之不停進行右移操作,最終結果一定可以得到 0;如果運算元是負數,對之不停進行右移操作,最終結果一定可以得到 -1

匿名函式lambda

匿名函式 lambda 是指一類無需定義識別符號(函式名)的函式或子程式。
lambda 函式可以接收任意多個引數 (包括可選引數) 並且返回單個表示式的值。

語法:

lambda [arg1,arg2,.....argn]:expression

冒號前是引數,可以有多個,用逗號隔開,冒號右邊的為表示式(只能為一個)。其實lambda返回值是一個函式的地址,也就是函式物件。

lambda arg: print("hello,world",arg)

lambda表示式限制只能包含一行程式碼,但是其實可以利用元組或列表強行讓其包含多行。(但是這麼做會嚴重影響可讀性,除非萬不得已不要使用)

lambda :(
    print("hello"),
    print("world"),           
)

切片

t=[1,2,3,4,5]

print(t[1:])     取第二個到最後一個元素
結果:[2 3 4 5]

print(t[:])     取所有元素
結果:[1 2 3 4 5]

print(t[1:3])     取t[1]-t[2]
結果:[ 2 3 ]

print(t[:-1])     除了最後一個取全部
結果:[ 1 2 3 4 ]
 
print(t[::-1])     取從後向前(相反)的元素
結果:[ 5 4 3 2 1 ]
 
print(t[2::-1])     取從下標為2的元素翻轉讀取
結果:[ 3 2 1 ]

字串方法

join(iterable)

獲取可迭代物件(iterable)中的所有專案,並將它們連線為一個字串。

例項1:

myTuple = ("Bill", "Steve", "Elon")
x = "#".join(myTuple)
print(x)

'''
輸出:
Bill#Steve#Elon
'''

例項2:

myDict = {"name": "Bill", "country": "USA"}
mySeparator = "TEST"
x = mySeparator.join(myDict)
print(x)

'''
輸出:
nameTESTcountry
'''

註釋:在使用字典作為迭代器時,返回的值是鍵,而不是值。

split(separator, max)

將字串拆分為列表,您可以指定分隔符,預設分隔符是任何空白字元。若指定 max,列表將包含指定數量加一的元素。

例項1:

txt = "welcome to China"
x = txt.split()
print(x)

'''
輸出:
['welcome', 'to', 'China']
'''

例項2:

txt = "apple#banana#cherry#orange"
# 將 max 引數設定為 1,將返回包含 2 個元素的列表!
x = txt.split("#", 1)
print(x)

'''
輸出:
['apple', 'banana#cherry#orange']
'''

內建函式

enumerate()

用於將一個可遍歷的資料物件(如列表、元組或字串)組合為一個索引序列,同時列出資料和資料下標,一般用在 for 迴圈當中。

例項1:

>>>seq = ['one', 'two', 'three']
>>> for i, element in enumerate(seq):
...     print i, element
... 
0 one
1 two
2 three

例項2:

>>>seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1))       # 下標從 1 開始
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
exec()

exec 執行儲存在字串或檔案中的 Python 語句,相比於 eval,exec可以執行更復雜的 Python 程式碼。

# 單行語句字串
>>>exec('print("Hello World")')
Hello World

#  多行語句字串
>>> exec ("""for i in range(5):
...     print ("iter time: %d" % i)
... """)
iter time: 0
iter time: 1
iter time: 2
iter time: 3
iter time: 4

二、Python爬蟲

下面的學習方式是以爬取豆瓣top250 網頁進行開展的

基本流程: 爬取網頁—>解析資料—>儲存資料

1 requests庫

Requests是一個簡單方便的HTTP 庫。比Python標準庫中的urllib2模組功能強大。Requests 使用的是 urllib3,因此繼承了它的所有特性。Requests 支援使用cookie 保持會話,支援檔案上傳,支援自動確定響應內容的編碼,支援URLPOST 資料自動編碼。幫助我們輕鬆解決關於HTTP的大部分問題。

爬取網頁首先要學習requests庫或者urllib庫的使用,不然你無法看懂下面程式碼

2 爬取網頁

2.1 爬取豆瓣top250第一頁資料

#-*- coding =utf-8 -*-
import requests

def askUrl(url):
    head = { #模擬瀏覽器頭部資訊,向豆瓣伺服器傳送訊息
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.69 Safari/537.36 Edg/81.0.416.34"
    # 使用者代理:告訴豆瓣伺服器,我們是什麼型別的瀏覽器(本質上是告訴瀏覽器我們可以接收什麼水平的檔案內容)
    }
    html=""  #用來接收資料
    r = requests.get(url, headers = head) #get方式傳送請求
    html = r.text #接收資料
    print(html)  
    return html

if __name__ == "__main__": # main函式用於測試程式
    askUrl("https://movie.douban.com/top250?start=") #呼叫函式

可以看到成功的爬取到豆瓣top250第一頁的資料

image-20200327113957848

image-20200327113957848

2.2 爬取豆瓣top250前10頁資料

#-*- coding =utf-8 -*-
import requests

#爬取一個頁面
def askUrl(url):
    head = { #模擬瀏覽器頭部資訊,向豆瓣伺服器傳送訊息
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.69 Safari/537.36 Edg/81.0.416.34"
    # 使用者代理:告訴豆瓣伺服器,我們是什麼型別的瀏覽器(本質上是告訴瀏覽器我們可以接收什麼水平的檔案內容)
    }
    #html=""
    r = requests.get(url, headers = head)
    html = r.text
    print(html)

# 爬取所有頁面
def getData(baseurl):
    for i in range(0, 10):
        url = baseurl + str(i * 25)
        html = askUrl(url)

if __name__ == "__main__": # main函式用於測試程式
    baseurl = "https://movie.douban.com/top250?start="
    getData(baseurl)

可以看到排名250的夢之安魂曲也被成功爬取到

image-20200327115150029

image-20200327115150029

3 BeautifulSoup4庫

BeautifulSoup4和 lxml 一樣,Beautiful Soup 也是一個HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 資料。

以下只涉及基礎使用,詳情請看中文文件:Beautiful Soup 4.4.0 文件

假設有這樣一個baidu.html,放在py檔案目錄下,下面的例子都基於該html,具體內容如下:

<!DOCTYPE html>
<html>
<head>
    <meta content="text/html;charset=utf-8" http-equiv="content-type" />
    <meta content="IE=Edge" http-equiv="X-UA-Compatible" />
    <meta content="always" name="referrer" />
    <link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css" />
    <title>百度一下,你就知道 </title>
</head>
<body link="#0000cc">
  <div id="wrapper">
    <div id="head">
        <div class="head_wrapper">
          <div id="u1">
            <a class="mnav" href="http://news.baidu.com" name="tj_trnews"><!--新聞--></a>
            <a class="mnav" href="http://news.baidu.com" name="tj_trnews">新聞</a>
            <a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
            <a class="mnav" href="http://map.baidu.com" name="tj_trmap">地圖</a>
            <a class="mnav" href="http://v.baidu.com" name="tj_trvideo">影片</a>
            <a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">貼吧</a>
            <a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多產品 </a>
          </div>
        </div>
    </div>
  </div>
</body>
</html>

3.1 快速使用案例

# 匯入模組
from bs4 import BeautifulSoup

# 讀取html檔案資訊(在真實程式碼中是爬取的網頁資訊)
file = open("./baidu.html",'rb') #解析器
content = f.read()
f.close()

# 建立解析器
bs = BeautifulSoup(content,"html.parser")

# 輸出網頁內容:注:此內容已被縮排格式化(自動更正格式),其實這個是在上一步例項化時就已完成
print(bs)

#輸出網頁中title標籤中的內容
print(bs.title.string)

3.2 BeautifulSoup4主要解析器

解析器 使用方法 優勢 劣勢
Python標準庫 BeautifulSoup(markup, “html.parser”) Python的內建標準庫,執行速度適中,文件容錯能力強 Python 2.7.3 or 3.2.2前的版本中文件容錯能力差
lxml HTML 解析器 BeautifulSoup(markup, “lxml”) 速度快 文件容錯能力強 需要安裝C語言庫
lxml XML 解析器 BeautifulSoup(markup, [“lxml-xml”]) BeautifulSoup(markup, “xml”) 速度快 唯一支援XML的解析器 需要安裝C語言庫
html5lib BeautifulSoup(markup, “html5lib”) 最好的容錯性,以瀏覽器的方式解析文件,生成HTML5格式的文件 速度慢、不依賴外部擴充套件

3.2 BS4四大物件種類

BeautifulSoup4將複雜HTML文件轉換成一個複雜的樹形結構,每個節點都是Python物件,所有物件可以歸納為4種

  • Tag
  • NavigableString
  • BeautifulSoup
  • Comment
3.2.1 Tag

Tag通俗點講就是為了獲取HTML中的一個個標籤

from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

# 獲取title標籤的所有內容
print(bs.title)  #<title>百度一下,你就知道 </title>

# 獲取head標籤的所有內容
print(bs.head) 

# 獲取第一個a標籤的所有內容
print(bs.a) 

# 型別
print(type(bs.a)) # <class 'bs4.element.Tag'>


#bs 物件本身比較特殊,它的 name 即為 [document] 
print(bs.name) # [document]

# head #對於其他內部標籤,輸出的值便為標籤本身的名稱
print(bs.head.name)  # head

# 獲取a標籤裡的所有屬性,列印輸出來,得到的型別是一個字典。 
print(bs.a.attrs) 
# {'class': ['mnav'], 'href': 'http://news.baidu.com', 'name': 'tj_trnews'}

#還可以利用get方法,傳入屬性的名稱,二者是等價的
print(bs.a['class']) # 等價 bs.a.get('class') 

# 可以對這些屬性和內容等等進行修改
bs.a['class'] = "newClass"
print(bs.a) 

# 還可以對這個屬性進行刪除
del bs.a['class'] 
print(bs.a)
3.2.2 NavigableString

既然我們已經得到了標籤的內容,那麼問題來了,我們要想獲取標籤內部的文字怎麼辦呢?很簡單,用 .string 即可,例如

from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

#獲取title標籤中的字串
print(bs.title.string) #百度一下,你就知道 

# 型別
print(type(bs.title.string)) 
#<class 'bs4.element.NavigableString'>
3.3.3 BeautifulSoup

BeautifulSoup物件表示的是一個文件的內容。大部分時候,可以把它當作 Tag 物件,是一個特殊的 Tag,我們可以分別獲取它的型別,名稱,以及屬性,例如:

from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

#獲取整個文件
print(bs)

print(type(bs)) #<class 'bs4.BeautifulSoup'>
3.3.4 Comment

Comment 物件是一個特殊型別的 NavigableString 物件,其輸出的內容不包括註釋符號。

from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

print(bs.a)
# a標籤如下:
# <a class="mnav" href="http://news.baidu.com" name="tj_trnews"><!--新聞--></a>

print(bs.a.string) # 新聞  #不會輸出上面a標籤中的註釋符號

print(type(bs.a.string)) 
# <class 'bs4.element.Comment'>

3.3 遍歷文件數

.contents:獲取Tag的所有子節點,返回一個list

from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

print(bs.head.contents)  #獲取head下面的所有直接子節點,返回列表 
print(bs.head.contents[1 #用列表索引來獲取它的某一個元素

.children:獲取Tag的所有子節點,返回一個生成器

from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

for child in  bs.body.children:
    print(child)
.descendants 獲取Tag的所有子孫節點
.strings 如果Tag包含多個字串,即在子孫節點中有內容,可以用此獲取,而後進行遍歷
.stripped_strings 與strings用法一致,只不過可以去除掉那些多餘的空白內容
.parent 獲取Tag的父節點
.parents 遞迴得到父輩元素的所有節點,返回一個生成器
.previous_sibling 獲取當前Tag的上一個節點,屬性通常是字串或空白,真實結果是當前標籤與上一個標籤之間的頓號和換行符
.next_sibling 獲取當前Tag的下一個節點,屬性通常是字串或空白,真是結果是當前標籤與下一個標籤之間的頓號與換行符
.previous_siblings 獲取當前Tag的上面所有的兄弟節點,返回一個生成器
.next_siblings 獲取當前Tag的下面所有的兄弟節點,返回一個生成器
.previous_element 獲取解析過程中上一個被解析的物件(字串或tag),可能與previous_sibling相同,但通常是不一樣的
.next_element 獲取解析過程中下一個被解析的物件(字串或tag),可能與next_sibling相同,但通常是不一樣的
.previous_elements 返回一個生成器,可以向前訪問文件的解析內容
.next_elements 返回一個生成器,可以向後訪問文件的解析內容
.has_attr 判斷Tag是否包含屬性

詳情請看中文文件:Beautiful Soup 4.4.0 文件

3.4 文件的搜尋find_all()

name引數
from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

#字串過濾:會查詢與字串完全匹配的內容
t_list = bs.find_all("a")
t_list = bs.find_all("title")
print(t_list)

#正規表示式過濾:如果傳入的是正規表示式,那麼BeautifulSoup4會透過search()來匹配內容
import re
t_list = bs.find_all(re.compile("a"))
print(t_list)
函式引數
from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

#定義函式:傳入一個函式,根據函式的要求來搜尋
def name_is_exists(tag):
    return tag.has_attr("name")#搜尋包含name的標籤

t_list = bs.find_all(name_is_exists)
for item in t_list: #列印列表內容
     print(item)
keyword引數
from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

#搜尋id=head的內容
t_list = bs.find_all(id="head")
for item in t_list:
    print(item)
    
#搜尋class=manav的內容    
t_list = bs.find_all(class_="mnav")
for item in t_list: 
     print(item)
        
#搜尋連結的內容 
t_list = bs.find_all(href="http://news.baidu.com")
for item in t_list: 
     print(item)
text引數
from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

import re
#t_list = bs.find_all(text="hao123")
#t_list = bs.find_all(text=["hao123","貼吧","地圖"])
t_list = bs.find_all(text=re.compile("\d"))#查詢包含數字的文字
for item in t_list: 
     print(item)
limit 引數
from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

t_list = bs.find_all("a",limit=3)
for item in t_list: 
     print(item)

3.5 css選擇器

from bs4 import BeautifulSoup 
file = open('./baidu.html', 'rb') 
content = file.read() 
bs = BeautifulSoup(content,"html.parser") 

#透過標籤來查詢
t_list = bs.select('title')   

#透過類名(.表示類)來查詢
t_list = bs.select('.mnav')

#透過id(#表示id)來查詢
t_list = bs.select('#u1')

#透過屬性來查詢 查詢a標籤中class=bri的內容
t_list = bs.select("a[class='bri']")   

#透過父子標籤來查詢
t_list=bs.select("head > title")   

#透過兄弟標籤來查詢
t_list=bs.select(".mnav ~ .bri")   

for item in t_list: #列印列表內容
       print(item)
print(t_list[0].get_text()) #拿到t_list中的文字

4 re庫

正規表示式(Regular Expression)通常被用來匹配檢索替換分割那些符合某個模式(規則)的文字。

4.1 正規表示式常用運算子

運算子 說明 例項
. 表示除 “\n” 之外的任何單個字元。
[ ] 宇符集,對單個字元給出取值範圍 [abc]表示a,b,c;[a-z]表示a到z單個字元
[^ ] 非字符集,對單個字元恰給出排除範圍 [^abc]表示非a或非b或c的單個字元
* 前一個字元0次或無限次擴充套件 abc* 表示ab、abc、abcc、abcc等
+ 前一個字元1次或無限次擴充套件 abc+ 表示abc、abcc、abcc等
? 前一個字元0次或1攻擴充套件 abc? 表示ab、abc
| 左右表示式任意一個 abc|def 表示abc、def
擴充套件前一個字元m次 ab(2}c表示abbc
擴充套件前一個字元m至n次(含n) ab{1,2}c表示abc、abbc
^ 匹配字串開頭 ^abc表示abc且在一個字串的開頭
$ 匹配字串結尾 abc$表示abc且在一個字串的結尾
( ) 分組標記,內部只能使用|運算子 (abc)表示abc ,(abc|def)表示abc、def
\d 數字,等價於[0-9]
\w 單詞字元,等價於[A-Za-z0-9_ ]

4.2 re庫常用函式

函式 說明
re.compile() 返回一個正則物件的模式。
re. search() 在一個字串中搜素匹配正規表示式的第一個位置 ,返回match物件
re. match() 從一個字串的開始位置起匹配正規表示式,返回match物件
re. findall() 搜尋字串,以列表型別返回全部能匹配的子串
re. split() 將一個字串按照正規表示式匹配結果進行分割,返回列表型別
re. finditer() 擅索字串。返回一個匹配結果的迭代型別,每個選代元素是match物件
re. sub() 在一個字串中普換所有匹配正規表示式的子串,返回替換後的字元申
4.2.1 compile()

格式:re.compile(pattern[,flags=0])

  • pattern: 編譯時用的表示式字串。
  • flags: 編譯標誌位,用於修改正規表示式的匹配方式,如:re.I、re.S等
import re
pat=re.compile("A")
m=pat.search("CBA") #等價於 re.search(A,CBA)
print(m)#<re.Match object; span=(2, 3), match='A'>  表示匹配到了

m=pat.search("CBD")
print(m)  #None 表示沒匹配到
  • 在字串中尋找模式
  • 格式:re.search(pattern, string[, flags=0])
  • re.search函式會在字串內查詢模式匹配,只要找到第一個匹配然後返回,如果字串沒有匹配,則返回None。
  import re
  m = re.search("asd" , "ASDasd" )
  print(m)# <_sre.SRE_Match object at 0xb72cd6e8>  #匹配到了,返回MatchObject(True)
  
  
  m = re.search("asd" , "ASDASD" )
  print(m)                                 #沒有匹配到,返回None(False)
4.2.3 match()
  • 在字串開始處匹配模式
  • 格式:re.match(pattern, string[, flags=0])
import re
pat=re.compile( "a" ) 
print(pat.match( "Aasd" )) #輸出None

pat=re.compile( "A" ) 
print(pat.match( "Aasd" )) #輸出<_sre.SRE_Match object; span=(0, 1), match='A'>
  • 注:match和search一旦匹配成功,就是一個match object物件,而match object物件有以下方法:
    • group() 返回被 RE 匹配的字串
    • start() 返回匹配開始的位置
    • end() 返回匹配結束的位置
    • span() 返回一個元組包含匹配 (開始,結束) 的位置
4.2.3 findall()
  • 列表形式返回匹配項
  • 格式:re.findall(pattern, string[, flags=0])
  import re
  #前面字串是規則(正規表示式),後面字串是被校驗的字串
  print(re.findall("a","ASDaDFGAa"))      
  #[a,a] 	#列表形式返回匹配到的字串
  
  p = re.compile(r'\d+')
  print(p.findall('o1n2m3k4'))
  #執行結果如下:
  #['1', '2', '3', '4']
  
  print(re.findall("[A-Z]","ASDaDFGAa"))
  #[ A , S , D , D , F , G , A ] 
  
  print(re.findall("[A-Z]+","ASDaDFGAa"))
  #[ ASD , DFGA ]
  
  pat = re.compile("[A-Za-z]")
  print(pat.findall("ASDcDFGAa"))
  #[ A , S , D , c , D , F , G , A , a ]
4.2.4 re. split()
  • 按照能夠匹配的子串將string分割後返回列表。
  • 可以使用re.split來分割字串,如:re.split(r’\s+’, text);將字串按空格分割成一個單詞列表。
  • 格式:re.split(pattern, string[, maxsplit])
    • maxsplit: 用於指定最大分割次數,不指定將全部分割。
print(re.split('\d+','one1two2three3four4five5'))

# 執行結果如下:
# ['one', 'two', 'three', 'four', 'five', '']
4.2.5 finditer()
  • 搜尋string,返回一個順序訪問每一個匹配結果(Match物件)的迭代器。

  • 找到 RE 匹配的所有子串,並把它們作為一個迭代器返回。

  • 格式:re.finditer(pattern, string[, flags=0])

    import re
    iter = re.finditer(r'\d+','12 drumm44ers drumming, 11 ... 10 ...')
    for i in iter:
        print(i)
        print(i.group())
        print(i.span())
    
    '''
    # 執行結果如下:
    <_sre.SRE_Match object; span=(0, 2), match='12'>
    12
    (0, 2)
    <_sre.SRE_Match object; span=(8, 10), match='44'>
    44
    (8, 10)
    <_sre.SRE_Match object; span=(24, 26), match='11'>
    11
    (24, 26)
    <_sre.SRE_Match object; span=(31, 33), match='10'>
    10
    (31, 33)
    '''
    
4.2.6 sub()
  • 格式:re.sub(pattern, repl, string, count)

  • 用repl替換 pattern匹配項

    import re
    print(re.sub(a,A,abcasd)) #找到a用A替換,後面見和group的配合使用
    #AbcAsd  #第四個引數指替換個數。預設為0,表示每個匹配項都替換。
    
    text = "JGood is a handsome boy, he is cool, clever, and so on..."
    print(re.sub(r'\s+', '-', text)) #\s:空格
    #JGood-is-a-handsome-boy,-he-is-cool,-clever,-and-so-on...
    

4.3 模式修正符

所謂模式修正符,即可以在不改變正規表示式的情況下,透過模式修正符改變正規表示式的含義,從而實現一些匹配結果的調整等功能。

修飾符 描述
re.I 使匹配對大小寫不敏感
re.L 做本地化識別(locale-aware)匹配
re.M 多行匹配,影響 ^ 和 $
re.S 使 . 匹配包括換行在內的所有字元
re.U 根據Unicode字符集解析字元。這個標誌影響 \w, \W, \b, \B.
re.X 該標誌透過給予你更靈活的格式以便你將正規表示式寫得更易於理解。
import re
string = "Python"
pat = "pyt"
rst = re.search(pat,string,re.I) # 第三個引數
print(rst)#<_sre.SRE_Match object; span=(0, 3), match='Pyt'>

相關文章