python-爬蟲面試題整理 自用 抄的知乎的 只是方便自己看


 

 

一、Python 基礎部分
1.1 Python 基本功
1.1.1 簡述Python 的特點和優點

Python 是一門開源的解釋性語言,相比 Java C++ 等語言,Python 具有動態特性,非常靈活。

1.1.1 Python 有哪些數據類型?

Python 有 6 種內置的數據類型,其中不可變數據類型是Number(數字), String(字符串), Tuple(元組),可變數據類型是 List(列表),Dict(字典),Set(集合)。

1.1.3 列表和元組的區別

列表和元組都是可迭代對象,能夠對其進行循環、切片等,但元組 tuple 是不可變的。元組不可變的特性,使得它可以成為字典 Dict 中的鍵。

1.1.4 Python 是如何運行的

CPython:

Python 程序運行時,會先進行編譯,將 .py 文件中的代碼編譯成字節碼(byte code),編譯結果儲存在內存的 PyCodeObject 中,然后由 Python 虛擬機解釋運行。當程序運行結束后,Python 解釋器會將 PyCodeObject 保存到 pyc 文件中。每一次運行時 Python 都會先尋找與文件同名的 pyc 文件,如果 pyc 存在則比對修改記錄,根據修改記錄決定直接運行或再次編譯后運行,最后生成 pyc 文件 。

 

1.1.5 Python 運行速度慢的原因

a). Python 不是強類型的語言,所以解釋器運行時遇到變量以及數據類型轉換、比較操作、引用變量時都需要檢查其數據類型。

b). Python 的編譯器啟動速度比 JAVA 快,但幾乎每次都要啟動編譯。

c). Python 的對象模型會導致訪問內存效率變低。Numpy 的指針指向緩存區數據的值,而 Python 的指針指向緩存對象,再通過緩存對象指向數據:

 

1.1.6 面對 Python 慢的問題,有什么解決辦法

a). 可以使用其他的解釋器,比如 PyPy 和 Jython 等。

b). 如果對性能要求較高且靜態類型變量較多的應用程序,可以使用 CPython。

c). 對於 IO 操作多的應用程序,Python 提供 asyncio 模塊提高異步能力。

 

1.1.7 描述一下全局解釋器鎖 GIL

每個線程在執行時候都需要先獲取 GIL,保證同一時刻只有一個線程可以執行代碼,即同一時刻只有一個線程使用 CPU,也就是說多線程並不是真正意義上的同時執行。但是在 IO 操作時,是可以釋放鎖的(這也是 Python 能夠異步的原因)。而且如果想要利用多核 CPU,那么可以使用多進程。

 

1.1.8 深拷貝 淺拷貝

深拷貝是將對象本身復制給另一個對象,淺拷貝則是將對象的引用復制給另一個對象。所以當復制后的對象改變時,深拷貝的原對象值不會改變,而淺拷貝原對象的值會被改變。

 

1.1.9 is 和 == 的區別

is 表示的是對象標示符(object identity),而 == 表示的是相等(equality)。

is 的作用是用來檢查對象的標示符是否一致,也就是比較兩個對象在內存中的地址是否一樣,而 == 是用來檢查兩個對象是否相等。但是為了提高系統性能,對於較小的字符串 Python 會保留其值的一個副本,當創建新的字符串的時候直接指向該副本即可。如:

a = 8
b = 8
a is b
1.1.10 文件讀寫

簡述文件讀取時 read 、readline、readlines 的區別和作用

他們的區別除了讀取內容范圍不同外,返回的內容類型也不同。

  • read()會讀取整個文件,將讀取到底的文件內容放到一個字符串變量,返回 str 類型。
  • readline()讀取一行內容,放到一個字符串變量,返回 str 類型。
  • readlines() 讀取文件所有內容,按行為單位放到一個列表中,返回 list 類型。
1.1.11 請用一行代碼實現

請分別使用匿名函數和推導式這兩種方式將 [0, 1, 2, 3, 4, 5] 中的元素求乘積,並打印輸出元組。

print( tuple(map(lambda x: x * x, [0, 1, 2, 3, 4, 5])))
 
print(tuple(i*i for i in [ 0, 1, 2, 3, 4, 5]))
1.1.12 請用一行代碼實現

用 reduce 計算 n 的階乘(n!=1×2×3×...×n)

print( reduce(lambda x, y: x*y, range(1, n)))
1.1.13 請用一行代碼實現

篩選並打印輸出 100 以內能被 3 整除的數的集合

print( set(filter(lambda n: n % 3 == 0, range(1, 100))))

1.1.14 請用一行代碼實現

text = 'Obj{ "Name": "pic", "data": [{"name": "async", "number": 9, "price": "$3500"}, {"name": "Wade", "number": 3, "price": "$5500"}], "Team": "Hot"'

打印文本中的球員身價元組,如 ($3500, $5500)

print(tuple(i.get( "price") for i in json.loads(re.search(r'\[(.*)\]', text).group(0))))
 
1.1.14 請寫出遞歸的基本骨架
def recursions(n):
if n == 1:
# 退出條件
return 1
# 繼續遞歸
return n * recursions(n - 1)
1.1.15 切片

請寫出下方輸出結果

tpl = [ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
 
print(tpl[3:])
print(tpl[:3])
print(tpl[::5])
print(tpl[-3])
print(tpl[3])
print(tpl[::-5])
print(tpl[:])
del tpl[3:]
print(tpl)
print(tpl.pop())
tpl.insert( 3, 3)
print(tpl)
 

 

[ 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
[ 0, 5, 10]
[ 0, 25, 50, 75]
85
15
[ 95, 70, 45, 20]
[ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
[ 0, 5, 10]
10
[ 0, 5, 3]
1.1.6 文件路徑

打印輸出當前文件所在目錄路徑

import os
print( os.path.dirname(os.path.abspath(__file__)))

打印輸出當前文件路徑

import os
print( os.path.abspath(__file__))

打印輸出當前文件上兩層文件目錄路徑

import os
print( os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
1.1.17 請寫出運行結果,並回答問題
tpl = ( 1, 2, 3, 4, 5)
apl = ( 6, 7, 8, 9)
print(tpl.__add__(apl))

問題:tpl 的值發生變化了嗎?

 

運行結果如下:

( 1, 2, 3, 4, 5, 6, 7, 8, 9)

 

答:元組是不可變的,它是生成新的對象

 

1.1.18 請寫出運行結果,並回答問題
name = ( 'James', 'Wade', 'Kobe')
team = [ 'A', 'B', 'C']
 
tpl = {name: team}
print(tpl)
apl = {team: name}
print(apl)

問題:這段代碼能運行完畢嗎?為什么?它的運行結果是?

答:這段代碼不能完整運行,它會在 apl 處拋出異常,因為字典的鍵只能是不可變對象,而 list 是可變的,所以不能作為字典的鍵。運行結果是:

{( 'James', 'Wade', 'Kobe'): ['A', 'B', 'C']}
TypeError
1.1.19 裝飾器

請寫出裝飾器代碼骨架

def log(func):
def wrapper(*args, **kw):
print( 'call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

簡述裝飾器在 Python 中的作用:

在不改動原函數代碼的情況下,為其增加新的功能。

 

1.1.20 多進程 多線程

多進程更穩定還是多線程更穩定?為什么?

多進程更穩定,它們是獨立運行的,不會因為一個崩潰而影響其他進程。

多線程的致命缺點是什么?

因為所有線程共享進程的內存,所以任何一個線程掛掉都可能直接造成整個進程崩潰。

進程間通信有哪些方式?

共享變量、隊列、管道。
1.2 Python 細節問題
1.2.1 連接字符串用join還是+

當用操作符+連接字符串的時候,每執行一次+都會申請一塊新的內存,然后復制上一個+操作的結果和本次操作的右操作符到這塊內存空間,因此用+連接字符串的時候會涉及好幾次內存申請和復制。而join在連接字符串的時候,會先計算需要多大的內存存放結果,然后一次性申請所需內存並將字符串復制過去,這是為什么join的性能優於+的原因。所以在連接字符串數組的時候,應考慮優先使用join。

 

1.2.2 Python 垃圾回收機制

參考 https://blog.csdn.net/xiongchengluo1129/article/details/80462651

 

Python中的垃圾回收是以引用計數為主,分代收集為輔。引用計數的缺陷是循環引用的問題。

在Python中,如果一個對象的引用數為0,Python虛擬機就會回收這個對象的內存。

 

引用計數法的原理是每個對象維護一個ob_refcnt,用來記錄當前對象被引用的次數,也就是來追蹤到底有多少引用指向了這個對象,當對象被創建、對象被引用、對象被傳入函數、被存儲在容器中等四種情況時,該對象的引用計數器 +1

  1. 對象被創建 a=14
  1. 對象被引用 b=a
  1. 對象被作為參數,傳到函數中 func(a)
  1. 對象作為一個元素,存儲在容器中 List={a,”a”,”b”,2}

與上述情況相對應,當發生對象別名被 del 銷毀時、對象的引用被賦予新對象時、漢書執行完畢后、從容器中刪除時等四種情況,該對象的引用計數器-1

  1. 當該對象的別名被顯式銷毀時 del a
  1. 當該對象的引別名被賦予新的對象, a=26
  1. 一個對象離開它的作用域,例如 func函數執行完畢時,函數里面的局部變量的引用計數器就會 -1(但是全局變量不會)。
  1. 將該元素從容器中刪除時,或者容器被銷毀時。

當指向該對象的內存的引用計數器為0的時候,該內存將會被Python虛擬機釋放.

sys.getrefcount(a)可以查看 a 對象的引用計數,但是比正常計數大1,因為調用函數的時候傳入a,這會讓 a 的引用計數+1

引用計數的優點:

  1. 高效
  1. 運行期沒有停頓:一旦沒有引用,內存就直接釋放了。不用像其他機制等到特定時機。實時性還帶來一個好處:處理回收內存的時間分攤到了平時。
  1. 對象有確定的生命周期
  1. 易於實現

引用計數的缺點:

  1. 維護引用計數消耗資源,維護引用計數的次數和引用賦值成正比,而不像mark and sweep等基本與回收的內存數量有關。
  1. 無法解決循環引用的問題。A和B相互引用而再沒有外部引用A與B中的任何一個,它們的引用計數都為1,但顯然應該被回收。 
# 循環引用示例
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)

為了解決這兩個缺點 Python 還引入了另外的機制:標記清除和分代回收.

標記清除

『標記清除(Mark—Sweep)』算法是一種基於追蹤回收(tracing GC)技術實現的垃圾回收算法。它分為兩個階段:第一階段是標記階段,GC會把所有的『活動對象』打上標記,第二階段是把那些沒有標記的對象『非活動對象』進行回收。那么GC又是如何判斷哪些是活動對象哪些是非活動對象的呢?

 

對象之間通過引用(指針)連在一起,構成一個有向圖,對象構成這個有向圖的節點,而引用關系構成這個有向圖的邊。從根對象(root object)出發,沿着有向邊遍歷對象,可達的(reachable)對象標記為活動對象,不可達的對象就是要被清除的非活動對象。根對象就是全局變量、調用棧、寄存器。

在上圖中,我們把小黑圈視為全局變量,也就是把它作為root object,從小黑圈出發,對象1可直達,那么它將被標記,對象2、3可間接到達也會被標記,而4和5不可達,那么1、2、3就是活動對象,4和5是非活動對象會被GC回收。

 

標記清除算法作為Python的輔助垃圾收集技術主要處理的是一些容器對象,比如list、dict、tuple,instance等,因為對於字符串、數值對象是不可能造成循環引用問題。

 

Python使用一個雙向鏈表將這些容器對象組織起來。不過,這種簡單粗暴的標記清除算法也有明顯的缺點:清除非活動的對象前它必須順序掃描整個堆內存,哪怕只剩下小部分活動對象也要掃描所有對象。 

分代回收

分代回收同樣作為Python的輔助垃圾收集技術處理那些容器對象。

GC 的邏輯

分配內存
-> 發現超過閾值了
-> 觸發垃圾回收
-> 將所有可收集對象鏈表放到一起
-> 遍歷, 計算有效引用計數
-> 分成 有效引用計數=0 和 有效引用計數 > 0 兩個集合
-> 大於0的, 放入到更老一代
-> =0的, 執行回收
-> 回收遍歷容器內的各個元素, 減掉對應元素引用計數(破掉循環引用)
-> 執行-1的邏輯, 若發現對象引用計數=0, 觸發內存回收
-> python底層內存管理機制回收內存

 

 Python 中, 一個代就是一個鏈表, 所有屬於同一”代”的內存塊都鏈接在同一個鏈表中用來表示“代”的結構體是 gc_generation, 包括了當前代鏈表表頭、對象數量上限、當前對象數量。

 

Python默認定義了三代對象集合,索引數越大,對象存活時間越長,新生成的對象會被加入第0代,前面_PyObject_GC_Malloc中省略的部分就是Python GC觸發的時機。每新生成一個對象都會檢查第0代有沒有滿,如果滿了就開始着手進行垃圾回收。

 

分代回收是一種以空間換時間的操作方式,Python將內存根據對象的存活時間划分為不同的集合,每個集合稱為一個代,Python將內存分為了3“代”,分別為年輕代(第0代)、中年代(第1代)、老年代(第2代),他們對應的是3個鏈表,它們的垃圾收集頻率與對象的存活時間的增大而減小。新創建的對象都會分配在年輕代,年輕代鏈表的總數達到上限時,Python垃圾收集機制就會被觸發,把那些可以被回收的對象回收掉,而那些不會回收的對象就會被移到中年代去,依此類推,老年代中的對象是存活時間最久的對象,甚至是存活於整個系統的生命周期內。同時,分代回收是建立在標記清除技術基礎之上。

 

1.2.3 遞歸

Python 遞歸深度默認是多少?遞歸深度限制的原因是什么?

Python 遞歸深度可以用內置函數庫中的 sys.getrecursionlimit() 查看。
因為無限遞歸會導致的 C 堆棧溢出和 Python 崩潰。

 

 

 

 

 

二、Python 進階知識

2.1

2.1.1 __new__ __init__ 的區別

 

2.2

 

2.2.1 請編寫一個用於文件讀寫的上下文管理器

 

2.2.2 請編寫一個用於文件讀寫的異步上下文管理器,並編寫 propty 提供外部訪問

 
三、爬蟲基礎部分
 
四、爬蟲框架部分
 
五、爬蟲進階知識
 
六、數據庫部分
 
七、算法與數據結構

7.1

7.1.1 時間復雜度

假設現有騰訊體育中熱火隊 11 名球員、搜狐體育中熱火隊 13 名球員要將兩個隊伍中球員依次拿出來進行屬性匹配,以確定同一球員在兩個網站中的不同 url id。

請說明比對的時間復雜度並畫出比對思路

 
八、HTTP 協議 WebSocket 協議
 
九、爬蟲工作的見解與展望


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM