回覆列表
  • 1 # 初九未成

    python爬蟲,我用最多的是框架Scrapy,其次便是beautiful soup,以及selenium、Requests庫等,最基礎的就是urllib和正則re了。

    當然,我不知道題主為什麼要問內建函式,所謂內建函式,一般都是因為使用比較頻繁或是元操作,然後提前定義好,直接呼叫。這樣的內建函式,python裡面有很多,大概可以分為基本的資料操作、邏輯操作、集合操作、基本IO操作、反射操作、字串操作等。它們隨著python直譯器的執行而建立,在Python的程式中,你可以隨時呼叫這些函式,不需要定義。

    仔細想來,我也不好說哪些內建函式就一定是為爬蟲而生的,它們只是在程式碼的某個環節小用了一下而已,主要還是靠爬蟲相關的包來支撐。

    但如果追根溯源,題主可以去參考python的原始碼,現在python原始碼已經遷移到GitHub上了,不過由於是用C寫的,故看起來會比較費神。原始碼地址:https://github.com/python/cpython

    下面就講講爬蟲常用到的一些模組包好了。

    re、urllib、time標準庫

    time可以用來延時,以免爬蟲程式碼被封,而正則表示式re和urllib模組則是學習python爬蟲最基礎的,也是最重要的。

    urllib模組提供了豐富的上層介面,使我們可以處理跟url相關的大多數事務,包括設定Headers、Proxy 、錯誤解析、Cookie 等,從而像讀取本地檔案一樣讀取www和ftp上的資料,包括文字、圖片和影片,同時也涉及到許多HTTP 協議相關知識。

    而正則表示式相信學過高階語言的朋友都不會陌生了,當用urllib提取到目標頁面的資訊後,我們需要用正則表示式來匹配查詢,獲得最終的內容,然後進行下一步的處理。

    這裡需要提一下,urllib模組在python2和python3中變化很大,上面是用python3寫的,學習的時候注意一下。

    requests

    當然,如果你把urllib和re用熟了,再來學用requests庫的話,你會感覺眼前豁然開朗,它基於 urllib開發,比urllib用起來更簡單順手,函式功能更強大,是一個很實用的Python HTTP客戶端庫,在編寫爬蟲和測試伺服器響應資料時經常會用到。同時,requests 的設計哲學是以 PEP 20 的習語為中心開發的,所以它比 urllib 更加 Pythoner(如果你不知道什麼是pythoner,可以輸入程式碼:import this)。

    有趣的是,現在requests的官方文件出中文版了,十分詳盡,言語風趣幽默:http://cn.python-requests.org/zh_CN/latest/index.html

    Beautiful Soup和 lxml、Selenium和PhantomJS、PyQuery等

    從這裡開始,就要步入爬蟲真正的門檻了,上面的這些工具都可以同時學,體驗一下。同時,考慮到我們爬取的網頁內容可能有靜態的、動態的,甚至還有將資料壓縮的網站,除此之外,還涉及到網頁需要登入,登入需要驗證碼,驗證碼的難易程度,還有付費與免費資源的區別等等!這些都是在這一階段必須要考慮和解決的問題了。

    Beautiful Soup 是解析網頁的一款神器。它可以從 HTML 或者 XML 檔案中提取資料;Xpath 也是一款神器。它是一款高效的、表達清晰簡單的分析語言。掌握它以後介意棄用正則表示式了。一般是使用瀏覽器的開發者工具加 lxml 庫。

    PhantomJS是一款沒有介面的瀏覽器,Selenium便是瀏覽器驅動,他們倆配合使用,可以爬取那些動態載入的網頁,當然,測試的時候還是可以使用Selenium+Chrome的。

    若是提到驗證碼識別,那涉及的就多了,不過,在爬蟲領域,你先需要了解的,也不算多,可以瞭解一下PIL+Tesseract,一個是影象處理,一個便是訓練和識別驗證碼的庫,這裡有很多難點,需要多查資料學習。

    如果你對js熟悉,又來做爬蟲,那麼PyQuery對你來說就是最友好的了, 它是仿照 jQuery ,語法與 jQuery 幾乎完全相同,所以不用再去費心去記一些奇怪的方法了,這樣解析起網頁來就更得心應手了。

    多執行緒threading和多程序muiltprocessing

    有人說, Python 的多執行緒是雞肋,不是真正意義上的多執行緒?但以我的親身實踐來看,開的執行緒達到10以上,甚至50,那肯定對效率是有很大提升的。

    所以,到了這一步,你就把他用起來吧!別管那麼多。

    終極利器Scrapy框架、PySpider框架等

    Scrapy 是一個功能非常強大的分散式爬蟲框架,學會了它,就可以不用重複造輪子,但基礎還是要一步一步來。

    當然,爬蟲框架越來越多,如果非要做一個比較,pyspider上手簡單,操作簡便,它增加了 WEB 介面,寫爬蟲迅速,且集成了phantomjs,可以用來抓取js渲染的頁面。

    而Scrapy自定義程度相對較高,比 PySpider更底層一些,適合學習研究,需要學習的相關知識多,不過自己拿來研究分散式和多執行緒等等是非常合適的。

    最後,給大家推薦一個學習爬蟲的部落格

    python爬蟲系列課程推薦:https://cuiqingcai.com/1052.html

    祝君進步!新年快樂!

  • 2 # 千鋒大前端酷炫世界

      函式

      一.引言

      a) 概念:函式是完成指定或者特定任務的一組程式碼。在面向物件程式設計的類中函式通常被稱為方法。不同的函式在程式中會扮演不同的角色完成不同的功能。

      b) 作用:

      i. 函式的使用可以提高程式碼的複用性,省去重複程式碼的編寫,提升程式程式碼重複利用率。

      ii. 函式能封裝內部的實現,保護內部的資料。通常,我們可以將函式看做成一個“黑盒子”。往往函式的使用者並不是函式的編寫者,函式的使用者並不需要對函式內部實現和行為進行考慮,可以將精力更多投入到自身業務邏輯的設計中。只有函式的編寫者才需要考慮函式內部的實現細節,如何暴露對外的介面,返回什麼樣的資料值,也就是API的設計。

      iii. 提高程式的可讀性。使得程式模組化。從一盤散散沙變成整齊佇列。

      iv. 提高程式的可維護性。案例:列印一個不同字元組成的分隔符。

      二.函式基礎

      a) 可以將函式抽象成現實生活中的工具。工具需要先製作出來才可以使用。函式也是一樣的需要先進行函式的定義,然後方可進行函式呼叫。

      b) 函式定義語法結構:

      def 函式名(引數):

      # 內部程式碼

      return 表示式

      c) Return:return不是必須要寫的,如果需要則寫,不需要可不寫。Return後面什麼都不跟表示return None。一旦函式執行過程中遇到了return語句,那麼函式中return後面的所有語句將不會執行,函式執行結束。

      d) 函式呼叫:

      i. 語法結構:函式名(引數值)

      ii. 注意事項:

      1. 引數之前使用逗號隔開

      2. 由於python動態語言的特點,函式呼叫時填寫的引數,python不會對引數型別進行檢查,如果函式呼叫中的引數不符合函式內部執行機制的話,會報錯。

      e) 引數:現實函式和呼叫者之間的互動

      i. 函式-》工具:使用ATM取錢時需要傳入密碼資料。

      ii. 實參和形參的概念。

      iii. 函式呼叫時,實參會傳值給形參

      1. 注意:

      a) Python的函式引數傳遞實際上傳遞的是實參的地址

      b) Python中的引數型別分為可變資料型別和不可變資料型別。

      2. Test:使用可變資料型別和不可變資料型別的資料作為引數

      a = 1

      def func(a):

      print("在函式內部修改之前,變數a的記憶體地址為: %s" % id(a))

      a = 2

      print("在函式內部修改之後,變數a的記憶體地址為: %s" % id(a))

      print("函式內部的a為: %s" % a)

      print("呼叫函式之前,變數a的記憶體地址為: %s" % id(a))

      func(a)

      print("函式外部的a為:%s" % a)

      列印結果為:

      呼叫函式之前,變數a的記憶體地址為: 1401140288

      在函式內部修改之前,變數a的記憶體地址為: 1401140288

      在函式內部修改之後,變數a的記憶體地址為: 1401140320

      函式內部的a為: 2

      函式外部的a為:1

      解釋:作為引數,a被傳入函式時,將數字物件1的地址傳遞給了函式內部的a。執行第一句內部程式碼時,此時內部的a和外面的a其實是一個東西,因此打印出了同樣的記憶體地址。而當a=2被執行後,由於整數是不可變的資料型別,所以建立了一個新的內部變數a,並賦值2,將數字物件2的記憶體地址賦給變數a。我們知道,首先,賦值語句具有建立新變數的功能。

      剛才說的是不可變型別引數,如果是可變型別的,比如列表呢?

      a = [1, 2, 3]

      def func(b):

      print("在函式內部修改之前,變數b的記憶體地址為: %s" % id(b))

      b.append(4)

      print("在函式內部修改之後,變數b的記憶體地址為: %s" % id(b))

      print("函式內部的b為: %s" % b)

      print("呼叫函式之前,變數a的記憶體地址為: %s" % id(a))

      func(a)

      print("函式外部的a為:%s" % a)

      執行結果是:

      呼叫函式之前,變數a的記憶體地址為: 34875720

      在函式內部修改之前,變數b的記憶體地址為: 34875720

      在函式內部修改之後,變數b的記憶體地址為: 34875720

      函式內部的b為: [1, 2, 3, 4]

      函式外部的a為:[1, 2, 3, 4]

      三.引數型別:python函式的引數定義靈活度很大,可以定義位置引數,預設引數,動態引數,關鍵字引數等。

      a) 位置引數

      i. 概念:也叫做必傳引數,在函式呼叫時必須明確提供的引數,切引數順序個數必須和形參保持一致。但是如果在函式呼叫的時候給實參指定引數名,那麼位置引數的順序可以不同。

      b) 預設引數:如果給某個引數提供一個預設值,該引數就是預設引數。在函式呼叫時,可以給預設引數傳遞一個值,也可以使用預設值。

      i. Test:

      def power(x, n = 2):

      return x**n

      ret1 = power(10) # 使用預設的引數值n=2

      ret2 = power(10, 4) # 將4傳給n,實際計算10**4的值

      ii. 使用預設引數的注意事項:

      1. 預設引數必須在順序引數後面

      2. 預設引數儘量指向不變的物件

      a) Test:

      下面是國內某上市網際網路公司Python面試真題:

      def func(a=[]):

      a.append("A")

      return a

      print(func())

      print(func())

      print(func())

      不要上機測試,僅憑程式碼,你能說出列印的結果嗎?

      很多同學可能會說,這還不簡單,肯定是下面的結果啊:

      ["A"]

      ["A"]

      ["A"]

      真的是這樣嗎?錯了!真正的結果是:

      ["A"]

      ["A", "A"]

      ["A", "A", "A"]

      Why?為什麼會這樣?

      因為Python函式體在被讀入記憶體的時候,預設引數a指向的空列表物件就會被建立,並放在記憶體裡了。因為預設引數a本身也是一個變數,儲存了指向物件[]的地址。每次呼叫該函式,往a指向的列表裡新增一個A。a沒有變,始終儲存的是指向列表的地址,變的是列表內的資料!我們可以測試一下:

      def func(a=[]):

      print("函式內部a的地址為:%s" % id(a))

      a.append("A")

      return a

      b = func()

      print("此時b的值為:%s" % b)

      print("函式外部b的地址為:%s" % id(b))

      print("-------------")

      c = func()

      print("此時c的值為:%s" % c)

      print("函式外部c的地址為:%s" % id(c))

      print("-------------")

      d = func()

      print("此時d的值為:%s" % d)

      print("函式外部d的地址為:%s" % id(d))

      列印結果是:

      函式內部a的地址為:39287880

      此時b的值為:["A"]

      函式外部b的地址為:39287880

      -------------

      函式內部a的地址為:39287880

      此時c的值為:["A", "A"]

      函式外部c的地址為:39287880

      -------------

      函式內部a的地址為:39287880

      此時d的值為:["A", "A", "A"]

      函式外部d的地址為:39287880

      那麼如何避免這個問題呢?

      使用不可變的資料型別作為預設值!

      def func(a=None):

      # 注意下面的if語句

      if a is None:

      a = []

      a.append("A")

      return a

      print(func())

      print(func())

      print(func())

      將預設引數a設定為一個類似None,數字或字串之類的不可變物件。在函式內部,將它轉換為可變的型別,比如空列表。這樣一來,不管呼叫多少次,執行結果都是["A"]了。

      iii. 動態引數

      1. 概念:函式呼叫時傳入的引數可以是動態數量的,可以為任意個,甚至是0個。

      2. 分類:重點是*的個數,*後的名字任意。動態引數必須放在所有位置引數和動態引數後面。

      a) *args:可以接受任意個引數。呼叫時,會將實參打包成一個元組傳給形參。如果引數是一個列表,則會將整個列表當做一個引數傳入。

      i. Test

      def func(*args):

      for arg in args:

      print(arg)

      func("a", "b", "c")

      li = [1, 2, 3]

      func(li)

      ii. 上述案例中,我們本意是將li這個列表中的元素作為引數進行傳值,但是實際卻是將列表本身作為一個整體進行了傳遞。如果想要將一個序列型別的物件(元組,列表,字串,字典)的元素依次作為引數進行傳遞,則可以在序列物件前加一個*即可。如果引數是字典,則會將字典所有的key逐一傳遞進去。

      1. Test:

      def func(*args):

      for arg in args:

      print(arg)

      li = [1, 2, 3]

      func(*li)

      b) **kwargs:表示接受鍵值對的動態引數,數量任意。呼叫時,會將引數打包成一個字典。

      i. Test:

      def func(**kwargs):

      for kwg in kwargs:

      print(kwg, kwargs[kwg])

      print(type(kwg))

      func(k1="v1", k2=[0, 1, 2])

      執行結果是:

      k1 v1

      

      k2 [0, 1, 2]

      

      ii. 而如果我們這樣傳遞一個字典dic呢?我們希望字典內的鍵值對能夠像上面一樣被逐一傳入。

      1. Test:

      def func(**kwargs):

      for kwg in kwargs:

      print(kwg, kwargs[kwg])

      dic = {

      "k1": "v1",

      "k2": "v2"

      }

      func(**dic)

      iv. 關鍵字引數:關鍵字引數前面需要一個特殊分隔符*和位置引數及預設引數分隔開來。*後面的引數被視為關鍵字引數。在函式呼叫時,關鍵字引數必須傳入引數名。

      1. Test:

      def student(name, age, *, sex):

      pass

      student(name="jack", age=18, sex="male")

      Test2:如果函式定義中已經有了一個*args引數,後面跟著的命名關鍵字引數就不再需要一個特殊分隔符*了。

      def student(name, age=10, *args, sex, classroom, **kwargs):

      pass

      student(name="jack", age=18, sex="male", classroom="202", k1="v1")

      總結:引數定義順序:位置引數,動態引數,關鍵字引數,動態引數

  • 中秋節和大豐收的關聯?
  • 肺癌術後飲食禁忌?