scrapy爬蟲框架


  這是我近期學習的一些內容,可能不僅僅局限於scrapy爬蟲框架,還會有很多知識的擴展。寫的可能不是那么有條理,想到什么就寫什么吧,畢竟也是自己以后深入學習的基礎,有些知識說的不夠明白歡迎留言,共同學習!

一、框架詳解

Scrapy是由Twisted寫的一個受歡迎的python事件驅動網絡框架,它使用的是非阻塞的異步處理。

1.內部各組件的作用

  ScrapyEngine(scrapy引擎):是用來控制整個系統的數據處理流程,並進行事務處理的觸發。

  Scheduler(調度器):用來接收引擎發過來的請求,壓入隊列中,並在引擎再次請求的時候返回。它就像是一個URL的優先隊列,由它來決定下一個要抓取的網址是什么,同時在這里會去除重復的網址。

  Downloader(下載器):用於下載網頁內容,並將網頁內容返回給爬蟲(Spiders)(Scrapy下載器是建立在Twisted這個高效的異步模型上的)。

  Spiders(爬蟲):爬蟲主要是干活的,用於從特定網頁中提取自己需要的信息,即所謂的實體(Item)。也可以從中提取URL,讓scrapy繼續爬取下一個頁面。

  Pipeline(項目管道):負責處理爬蟲從網頁中爬取的實體,主要的功能就是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被送到項目管道,並經過幾個特定的次序處理數據。

  Downloader Middlewares(下載器中間件):位於scrapy引擎和下載器之間的框架,主要是處理scrapy引擎與下載器之間的請求及響應。設置代理ip和用戶代理可以在這里設置。

  Spider Middlewares(爬蟲中間件):位於scrapy引擎和爬蟲之間的框架,主要工作是處理爬蟲的響應輸入和請求輸出。

  Scheduler Middlewares(調度中間件):位於scrapy引擎和調度器之間的框架,主要是處理從scrapy引擎發送到調度器的請求和響應。

2.Scrapy運行流程

  引擎從調度器中取出一個URL用於接下來的抓取

  引擎把URL封裝成一個請求(Request)傳給下載器

  下載器把資源下載下來,並封裝成一個響應(Response)

  爬蟲解析Response

  解析出的是實體(Item),則交給項目管道(Pipeline)進行進一步的處理

  解析出的是鏈接(URL),則把URL交給調度器等待下一步的抓取

二、為什么使用scrapy?爬蟲能做什么?

1.Scrapy vs requests+beautifulsoup

  首先,requests和beautifulsoup都是庫,scrapy是框架;在scrapy框架中可以加入requests和beautifulsoup,它是基於Twisted(異步非阻塞)實現的,性能上有很大的優勢;scrapy方便擴展,提供了很多內置的功能;它內置的css和Xpath以及selector提取數據的時候非常高效,beautifulsoup最大的缺點就是慢。

2.爬蟲能做什么?

  搜索引擎(如百度、Google)、推薦引擎(如今日頭條)、機器學習的數據樣本、數據分析(如金融數據分析)、輿情分析等

三、爬蟲基礎知識

1.正則表達式

為什么使用正則表達式?

  有時候,我們爬取一些網頁具體內容時,會發現我們只需要這個網頁某個標簽的一部分內容,或者這個標簽的某個屬性的值時,用xpath和css不太好提取數據,這時候我們就需要用到正則表達式去匹配提取。

re模塊簡介  

 1 import re
 2 r'''
 3 re.match函數
 4 原型:match(pattern, string, flags=0)
 5 參數:
 6 pattern:匹配的正則表達式
 7 string:要匹配的字符串
 8 flags:標志位,用於控制正則表達式的匹配方式,值如下
 9 re.I: 忽略大小寫
10 re.L: 做本地化識別
11 re.M: 多行匹配,只影響^和$,對每一行進行操作
12 re.S: 使.匹配包括換行符在內的所有字符
13 re.U: 根據Unicode字符集解析字符,影響\w \W \b \B
14 re.X: 使我們以更靈活的格式理解正則表達式
15 注意:多個選項同時使用時用"|"隔開,如:re.I|re.L|re.M|re.S|re.U|re.X
16 功能:嘗試從字符串的起始位置匹配一個模式,如果不是起始位置匹配,成功的話返回None
17 '''
18 # www.baidu.com
19 print(re.match('www', 'www.baidu.com'))
20 print(re.match('www', 'www.baidu.com').span())
21 print(re.match('www', 'wwW.baidu.com', flags=re.I))
22 # 返回None
23 print(re.match('www', 'ww.baidu.com'))
24 print(re.match('www', 'baidu.www.com'))
25 print(re.match('www', 'wwW.baidu.com'))
26 # 掃描整個字符串返回從起始位置成功的匹配
27 print('----------------------------------------------------------')
28 r'''
29 re.search函數
30 原型:search(pattern, string, flags=0)
31 參數:
32 pattern:匹配的正則表達式
33 string:要匹配的字符串
34 flags:標志位,用於控制正則表達式的匹配方式
35 功能:掃描整個字符串,並返回第一個成功的匹配
36 '''
37 print(re.search('sunck', 'good man is sunck!sunck is nice'))
38 
39 print('---------------------------------------------------------')
40 r'''
41 re.findall函數
42 原型:findall(pattern, string, flags=0)
43 參數:
44 pattern:匹配的正則表達式
45 string:要匹配的字符串
46 flags:標志位,用於控制正則表達式的匹配方式
47 功能:掃描整個字符串,並返回結果列表
48 '''
49 print(re.findall('sunck', 'good man is sunck!sunck is nice'))
50 print(re.findall('sunck', 'good man is sunck!Sunck is nice', flags=re.I))
51 
52 print('---------------------------------------------------------')
re模塊

常用的一些正則元字符

  1 import re
  2 print('------------------匹配單個字符與數字------------------')
  3 r'''
  4 .             匹配除換行符以外的任意字符
  5 *             匹配0個或多個重復的字符,只裝飾前面的一個字符    貪婪模式
  6 +             匹配1個或者多個重復的字符,只裝飾前面的一個字符   貪婪模式
  7 ?             匹配0個或者1個字符,只裝飾前面的一個字符
  8 [0123456789]  []是字符集合,表示匹配方括號中所包含的任意一個字符
  9 [sunck]       匹配's','u','n','c','k'中的任意一個字符
 10 [a-z]         匹配任意小寫字母
 11 [A-Z]         匹配任意大寫字母
 12 [0-9]         匹配任意數字,類似[0123456789]
 13 [0-9a-zA-Z]   匹配任意的數字和字母
 14 [0-9a-zA-Z_]  匹配任意的數字、字母和下划線
 15 [^sunck]      匹配除了's','u','n','c','k'這幾個字母以外的所有字符,中括號的"^"稱為脫字符,表示不匹配集合中的字符
 16 [^0-9]        匹配所有的非數字字符
 17 \d            匹配所有的數字,效果同[0-9]
 18 \D            匹配所有的非數字字符,效果同[^0-9]
 19 \w            匹配數字、字母和下划線,效果同[0-9a-zA-Z_]
 20 \W            匹配非數字、字母和下划線,效果同[^0-9a-zA-Z_]
 21 \s            匹配所有的空白符(空格、換行、回車、換頁、制表),效果同[ \r\n\f\t(v)]
 22 \S            匹配任意的非空白符,效果同[^ \r\n\f\t(v)]
 23 '''
 24 print(re.search('.', 'sunck is a good man'))
 25 print(re.search('[0123456789]', 'sunck is a good 3 man'))
 26 print(re.search('[0123456789]', 'sunck is a good man 6'))
 27 print(re.search('[sunck]', 'sunck is a good 3 man'))
 28 print(re.findall('[^\d]', 'sunck is a good 3 man'))
 29 print('------------------錨字符(邊界字符)------------------')
 30 r'''
 31 ^             行首匹配,和在[]里的^不是一個意思
 32 $             行尾匹配
 33 \A            匹配字符串開始,它和^的區別是:\A只匹配整個字符串的開頭,即使在re.M模式下也不會匹配它行的行首
 34 \Z            匹配字符串結尾,它和$的區別是:\Z只匹配整個字符串的結尾,即使在re.M模式下也不會匹配它行的行尾
 35 \b            匹配一個單詞的邊界,指單詞結束的位置能匹配,r'er\b'可以匹配never,不能匹配nerve,也就是-w和-W相鄰的時候是邊界     不轉義的是時候是刪除上一個字符
 36 \B            匹配非單詞的邊界,指單詞里面的位置能匹配,r'er\B'可以匹配nerve,不能匹配never
 37 '''
 38 print(re.search('^sunck', 'sunck is a good man'))
 39 print(re.search('sunck$', 'sunck is a good man'))
 40 print(re.search('man$', 'sunck is a good man'))
 41 print(re.search(r'er\b', 'never'))
 42 print(re.search(r'er\b', 'nerve'))
 43 print(re.search(r'er\B', 'never'))
 44 print(re.search(r'er\B', 'nerve'))
 45 print('------------------匹配多個字符------------------')
 46 r'''
 47 說明:下方的x、y、z均為假設的普通字符,n、m均為非負整數,不是正則表達式的元字符
 48 (xyz)         匹配小括號內的xyz(作為一個整體去匹配)
 49 x?            匹配0個或者1個x
 50 x*            匹配0個或者任意多個x,(.* 表示匹配0個或者任意多個字符(換行符除外))
 51 x+            匹配至少一個x
 52 x{n}          匹配確定的n個x(n是一個非負整數)
 53 x{n,}         匹配至少n個x
 54 x{n,m}        匹配至少n個最多m個x,注意:n <= m
 55 x|y           匹配的是x或y,|表示或 
 56 print(re.findall(r"a.*c|b[\w]{2}$", 'asacbas'))
 57 注意:多個正則表達式相或的時候,匹配過程是從左向右依次嘗試匹配,如果左邊有一個或者若干個匹配已經把整個字符串匹配完了,
 58 那么后面的表達式將不再進行匹配。匹配是從左向右依次使用正則表達式去匹配前面匹配后剩下來的字符串
 59 
 60 '''
 61 print(re.findall(r'(sunck)', 'sunckgood is a good man,sunck is a nice man'))
 62 print(re.findall(r'a?', 'aaa'))  # 非貪婪匹配(盡可能少的匹配)
 63 print(re.findall(r'a*', 'aaabbaaa'))  # 貪婪匹配(盡可能多的匹配)
 64 print(re.findall(r'a+', 'aaabbaaaaaaaaaaaa'))  # 貪婪匹配(盡可能多的匹配)
 65 # 需求,提取sunck----man
 66 str = 'sunck is a good man!sunck is a nice man!sunck is a very handsome man'
 67 print(re.findall(r'sunck.*?man', str))
 68 
 69 print('------------------特殊------------------')
 70 r'''
 71 *?  +?  x?  最小匹配  通常都是盡可能多的匹配,可以使用這種方式解決貪婪匹配
 72 (?:x)       類似(xyz),但不表示一個組,取消組的緩存,無法通過group()獲取其中的組
 73 (?P<name>)  給一個組起個別名,group()的時候可以直接使用該名字進行匹配
 74 (?P=name)   命名反向引用,可以直接使用前面命名的組
 75 (a|b)\1     匿名分組的反向引用,通過組號匹配(aa|bb)
 76 (a|b)(c|d)\1\2 (acac|adad|bcbc|bdbd)
 77 abc(?=123)  前向斷言,匹配前面緊跟着123的abc
 78 abc(?!123)  前向斷言,匹配前面不能是緊跟着123的abc
 79 (?<=123)abc 后向斷言,匹配后面緊跟着123的abc
 80 (?<!123)abc 后向斷言,匹配后面不能是緊跟着123的abc
 81 '''
 82 # 注釋: /* part1 */ /* part2 */
 83 print(re.findall(r'//*.*/*/', r'/* part1 */ /* part2 */'))
 84 print(re.findall(r'//*.*?/*/', r'/* part1 */ /* part2 */'))
 85 # 命名反向引用
 86 print(re.search(r'd(?P<first>ab)+(?P=first)', 'asagghhdababhjdkfdabjjk'))
 87 # 匿名分組的反向引用,通過組號匹配
 88 m = re.search(r'd(a|b)\1', 'daa|dbb')
 89 if m:
 90     print(m.group())
 91 m = re.search(r'd(a|b)(c|d)\1\2', 'dacac|dadad|dbcbc|dbdbd')
 92 if m:
 93     print(m.group())
 94 # 前向斷言
 95 m = re.search(r'abc(?=123)', 'abc123')
 96 if m:
 97     print(m.group())
 98 m = re.search(r'abc(?!123)', 'abc124')
 99 if m:
100     print(m.group())
101 # 后向斷言
102 m = re.search(r'(?<=123)abc', '123abc')
103 if m:
104     print(m.group())
105 m = re.search(r'(?<!123)abc', '123abc')
106 if m:
107     print(m.group())
108 
109 r'''
110 .    通配符,代表一個任意字符
111 '''
112 # print(re.findall(r'l..e', a))
113 r'''
114 *    0個或者多個重復的字符,只裝飾前面的一個字符   貪婪模式
115 '''
116 # print(re.findall(r'l.*e', a))
117 r'''
118 ^    以......開頭
119 '''
120 # print(re.findall(r'^I', a))
121 r'''
122 $    以......結尾
123 '''
124 # print(re.findall(r'^a.*b$', 'adfgfhb'))
125 r'''
126 +    1個或者多個重復的字符,只裝飾前面的一個字符   貪婪模式
127 '''
128 # print(re.findall(r'^aa+b$', 'aaaa2aab'))
129 r'''
130 ?    0個或者1個字符,只裝飾前面的一個字符   可以把貪婪模式轉換為非貪婪模式
131 '''
132 # print(re.findall(r'ab?', 'aaaa2aab'))
133 r'''
134 {m}    m個字符,只裝飾前面的一個字符  
135 '''
136 # print(re.findall(r'ab{5}', 'aaabbbbba2aab'))
137 r'''
138 {m,n}    至少m個最多n個字符,只裝飾前面的一個字符  
139 '''
140 # print(re.findall(r'ab{3,5}', 'aaabbbbba2aab'))
141 r'''
142 {m,}    至少m個字符,只裝飾前面的一個字符  
143 '''
144 # print(re.findall(r'ab{3,}', 'aaabbbbba2aab'))
145 r'''
146 []    是一個字符集合,在這里面元字符就失去原來的作用,成為普通字符
147 [^abc]   除了集合中的元素,其他的元素都可以匹配到
148 [^^]     只要不是^都可以匹配到
149 -     表示區間,如果想表示-寫到最前面或者最后面[a-],或者使用轉義'\'
150 '''
151 # print(re.findall(r'ab[a-z]', 'aaabbbbba2aab'))
152 r'''
153 \d{9}$ :表示以\d{9}結尾
154 \d            匹配所有的數字,效果同[0-9]
155 \D            匹配所有的非數字字符,效果同[^0-9]
156 \w            匹配數字、字母和下划線,效果同[0-9a-zA-Z_]
157 \W            匹配非數字、字母和下划線,效果同[^0-9a-zA-Z_]
158 \s            匹配所有的空白符(空格、換行、回車、換頁、制表),效果同[ \r\n\f\t(v)]
159 \S            匹配任意的非空白符,效果同[^ \r\n\f\t(v)]
160 '''
161 # print(re.findall(r"a.*c|b[\w]{2}$", 'asacbas'))
162 # 注意:多個正則表達式相或的時候,匹配過程是從左向右依次嘗試匹配,如果左邊有一個或者若干個匹配已經把整個字符串匹配完了,
163 # 那么后面的表達式將不再進行匹配。匹配是從左向右依次使用正則表達式去匹配前面匹配后剩下來的字符串
164 # 匹配特殊字符可以使用'\'或者'[]',* + ? [] () {}等特殊字符就變成一個普通字符了
165 # ret = re.findall(r"d(ab)+", 'asadacbdababadabs')
166 # print(ret)
167 # print(ret.group(1))
168 # print(ret.group(2))
元字符
 1 import re
 2 r'''
 3 字符串切割
 4 '''
 5 str = 'sunck    is a good   man'
 6 print(re.split(r' +', str))
 7 
 8 r'''
 9 re.finditer函數
10 原型:finditer(pattern, string, flags=0)
11 參數:
12 pattern:匹配的正則表達式
13 string:要匹配的字符串
14 flags:標志位,用於控制正則表達式的匹配方式
15 功能:與findall類似,掃描整個字符串,返回的是一個迭代器
16 '''
17 str1 = 'sunck is a good man!sunck is a nice man!sunck is a handsome man'
18 d = re.finditer(r'(sunck)', str1)
19 while True:
20     try:
21         L = next(d)
22         print(L)
23     except StopIteration as e:
24         break
25 
26 r'''
27 sunck is a good man 
28 字符串的替換和修改
29 sub(pattern, repl, string, count=0, flags=0)
30 subn(pattern, repl, string, count=0, flags=0
31 參數:
32 pattern: 正則表達式(規則)
33 repl:    指定的用來替換的字符串
34 string:  目標字符串
35 count:   最多替換次數
36 flags:   標志位,用於控制正則表達式的匹配方式
37 功能:在目標字符串中以正則表達式的規則匹配字符串,再把他們替換成指定的字符串。可以指定替換的次數,如果不指定,替換所有的匹配字符串 
38 區別:前者返回一個被替換的字符串,后者返回一個元組,第一個元素是被替換的字符串,第二個元素是被替換的次數
39 '''
40 str2 = 'sunck is a good good good man '
41 print(re.sub(r'(good)', 'nice', str2))
42 print(type(re.sub(r'(good)', 'nice', str2)))
43 print(re.subn(r'(good)', 'nice', str2))
44 print(type(re.subn(r'(good)', 'nice', str2)))
45 
46 r'''
47 分組:
48 概念:除了簡單的判斷是否匹配之外,正則表達式還有提取子串的功能。用()表示的就是提取出來的分組
49 
50 '''
51 str3 = '010-52347654'
52 m = re.match(r'(?P<first>\d{3})-(?P<last>\d{8})', str3)
53 # 使用序號獲取對應組的信息,group(0)一直代表的是原始字符串
54 print(m.group(0))
55 print(m.group(1))
56 print(m.group('first'))
57 print(m.group('last'))
58 print(m.group(2))
59 # 查看匹配的各組的情況
60 print(m.groups())
61 
62 r'''
63 編譯:當我們使用正則表達式時,re模塊會干兩件事
64 1、編譯正則表達式,如果正則表達式本身不合法,會報錯
65 2、用編譯后的正則表達式去匹配對象
66 compile(pattern, flags=0)
67 pattern:要編譯的正則表達式
68 flags:標志位,用於控制正則表達式的匹配方式
69 '''
70 pat = r'^1(([3578]\d)|(47))\d{8}$'
71 print(re.match(pat, '13600000000'))
72 # 編譯成正則對象
73 re_telephone = re.compile(pat)
74 print(re_telephone.match('13600000000'))
75 
76 # re模塊調用
77 # re對象調用
78 
79 # re.match(pattern, string, flags=0)
80 # re_telephone.match(string)
81 
82 # re.search(pattern, string, flags=0)
83 # re_telephone.search(string)
84 
85 # re.findall(pattern, string, flags=0)
86 # re_telephone.findall(string)
87 
88 # re.finditer(pattern, string, flags=0)
89 # re_telephone.finditer(string)
90 
91 # re.split(pattern, string, maxsplit=0, flags=0)
92 # re_telephone.split(string, maxsplit=0)
93 
94 # re.sub(pattern, repl, string, count=0, flags=0)
95 # re_telephone.sub(repl, string, count=0)
96 
97 # re.subn(pattern, repl, string, count=0, flags=0)
98 # re_telephone.subn(repl, string, count=0)
re模塊深入
  1 r'''
  2 JavaScript RegExp 對象
  3 RegExp 對象:
  4 正則表達式是描述字符模式的對象。正則表達式用於對字符串模式匹配及檢索替換,是對字符串執行模式匹配的強大工具。
  5 語法:var patt=new RegExp(pattern,modifiers);或者var patt=/pattern/modifiers;
  6 參數:
  7 pattern(模式) 描述了表達式的模式
  8 modifiers(修飾符) 用於指定全局匹配、區分大小寫的匹配和多行匹配
  9 注意:當使用構造函數創造正則對象時,需要常規的字符轉義規則(在前面加反斜杠 \)。比如,以下是等價的:
 10 var re = new RegExp("\\w+");
 11 var re = /\w+/;
 12 '''
 13 
 14 r'''
 15 修飾符:修飾符用於執行區分大小寫和全局匹配
 16 i    執行對大小寫不敏感的匹配。
 17 g    執行全局匹配(查找所有匹配而非在找到第一個匹配后停止)。
 18 m    執行多行匹配。
 19 '''
 20 
 21 r'''
 22 方括號:方括號用於查找某個范圍內的字符
 23 [abc]    查找方括號之間的任何字符。
 24 [^abc]    查找任何不在方括號之間的字符。
 25 [0-9]    查找任何從 0 至 9 的數字。
 26 [a-z]    查找任何從小寫 a 到小寫 z 的字符。
 27 [A-Z]    查找任何從大寫 A 到大寫 Z 的字符。
 28 [A-z]    查找任何從大寫 A 到小寫 z 的字符。
 29 [adgk]    查找給定集合內的任何字符。
 30 [^adgk]    查找給定集合外的任何字符。
 31 (red|blue|green)    查找任何指定的選項。
 32 '''
 33 
 34 r'''
 35 元字符:元字符(Metacharacter)是擁有特殊含義的字符
 36 .        查找單個字符,除了換行和行結束符。
 37 \w        查找單詞字符。
 38 \W        查找非單詞字符。
 39 \d        查找數字。
 40 \D        查找非數字字符。
 41 \s        查找空白字符。
 42 \S        查找非空白字符。
 43 \b        匹配單詞邊界。
 44 \B        匹配非單詞邊界。
 45 \0        查找 NUL 字符。
 46 \n        查找換行符。
 47 \f        查找換頁符。
 48 \r        查找回車符。
 49 \t        查找制表符。
 50 \v        查找垂直制表符。
 51 \xxx    查找以八進制數 xxx 規定的字符。
 52 \xdd    查找以十六進制數 dd 規定的字符。
 53 \uxxxx    查找以十六進制數 xxxx 規定的 Unicode 字符。
 54 '''
 55 
 56 r'''
 57 量詞
 58 n+        匹配任何包含至少一個 n 的字符串。
 59 n*        匹配任何包含零個或多個 n 的字符串。
 60 n?        匹配任何包含零個或一個 n 的字符串。
 61 n{X}    匹配包含 X 個 n 的序列的字符串。
 62 n{X,Y}    匹配包含 X 至 Y 個 n 的序列的字符串。
 63 n{X,}    匹配包含至少 X 個 n 的序列的字符串。
 64 n$        匹配任何結尾為 n 的字符串。
 65 ^n        匹配任何開頭為 n 的字符串。
 66 ?=n        匹配任何其后緊接指定字符串 n 的字符串。
 67 ?!n        匹配任何其后沒有緊接指定字符串 n 的字符串。
 68 '''
 69 
 70 r'''
 71 JavaScript search() 方法
 72 定義和用法:
 73 search() 方法用於檢索字符串中指定的子字符串,或檢索與正則表達式相匹配的子字符串。如果沒有找到任何匹配的子串,則返回 -1。
 74 語法:string.search(searchvalue)
 75 參數:
 76 searchvalue:必須。查找的字符串或者正則表達式。
 77 返回值:Number(類型)    
 78 與指定查找的字符串或者正則表達式相匹配的 String 對象起始位置。
 79 '''
 80 
 81 r'''
 82 JavaScript match() 方法
 83 定義和用法:
 84 match() 方法可在字符串內檢索指定的值,或找到一個或多個正則表達式的匹配。
 85 注意: match() 方法將檢索字符串 String Object,以找到一個或多個與 regexp 匹配的文本。這個方法的行為在很大程度上有賴於 regexp 是否具有標志 g。
 86 如果 regexp 沒有標志 g,那么 match() 方法就只能在 stringObject 中執行一次匹配。如果沒有找到任何匹配的文本, match() 將返回 null。
 87 否則,它將返回一個數組,其中存放了與它找到的匹配文本有關的信息。
 88 語法:string.match(regexp)
 89 參數:
 90 regexp:    必需。規定要匹配的模式的 RegExp 對象。如果該參數不是 RegExp 對象,則需要首先把它傳遞給 RegExp 構造函數,將其轉換為 RegExp 對象。
 91 返回值:Array(類型)
 92 存放匹配結果的數組。該數組的內容依賴於 regexp 是否具有全局標志 g。 如果沒找到匹配結果返回 null 。
 93 '''
 94 
 95 r'''
 96 JavaScript replace() 方法
 97 定義和用法:
 98 replace() 方法用於在字符串中用一些字符替換另一些字符,或替換一個與正則表達式匹配的子串。
 99 該方法不會改變原始字符串。
100 語法:string.replace(searchvalue,newvalue)
101 參數:
102 searchvalue:必須。規定子字符串或要替換的模式的 RegExp 對象。請注意,如果該值是一個字符串,則將它作為要檢索的直接量文本模式,而不是首先被轉換為 RegExp 對象。
103 newvalue:必需。一個字符串值。規定了替換文本或生成替換文本的函數。
104 返回值:String(類型)    
105 一個新的字符串,是用 replacement 替換了 regexp 的第一次匹配或所有匹配之后得到的。
106 '''
107 
108 r'''
109 JavaScript split() 方法
110 定義和用法:
111 split() 方法用於把一個字符串分割成字符串數組。
112 提示: 如果把空字符串 ("") 用作 separator,那么 stringObject 中的每個字符之間都會被分割。
113 注意: split() 方法不改變原始字符串。
114 語法:string.split(separator,limit)
115 參數:
116 separator:可選。字符串或正則表達式,從該參數指定的地方分割 string Object。
117 limit:可選。該參數可指定返回的數組的最大長度。如果設置了該參數,返回的子串不會多於這個參數指定的數組。如果沒有設置該參數,整個字符串都會被分割,不考慮它的長度。
118 返回值:Array(類型)
119 一個字符串數組。該數組是通過在 separator 指定的邊界處將字符串 string Object 分割成子串創建的。返回的數組中的字串不包括 separator 自身。
120 '''
121 
122 r'''
123 JavaScript compile() 方法
124 定義和用法:
125 compile() 方法用於在腳本執行過程中編譯正則表達式。compile() 方法也可用於改變和重新編譯正則表達式。
126 語法:RegExpObject.compile(regexp,modifier)
127 參數:
128 regexp:    正則表達式。
129 modifier:規定匹配的類型。"g" 用於全局匹配,"i" 用於區分大小寫,"gi" 用於全局區分大小寫的匹配。
130 '''
131 
132 r'''
133 JavaScript exec() 方法
134 定義和用法:
135 exec() 方法用於檢索字符串中的正則表達式的匹配。如果字符串中有匹配的值返回該匹配值,否則返回 null。
136 語法:RegExpObject.exec(string)
137 參數:
138 string:    Required. The string to be searched
139 '''
140 
141 r'''
142 JavaScript test() 方法
143 定義和用法:
144 test() 方法用於檢測一個字符串是否匹配某個模式。如果字符串中有匹配的值返回 true ,否則返回 false。
145 語法:RegExpObject.test(string)
146 參數:
147 string:    必需。要檢測的字符串。
148 '''
JavaScript正則表達式
 1 100--客戶必須繼續發送請求
 2 101--客戶要求服務器根據請求轉換HTTP協議版本
 3 200--交易成功***
 4 201--提示知道新文件的URL
 5 202--接收和處理,但處理未完成
 6 203--返回信息不確定或不完整
 7 204--請求收到,但返回信息為空
 8 205--服務器完成了請求,用戶代理必須復位當前已經瀏覽過的文件
 9 206--服務器已經完成了部分用戶的GET請求
10 300--請求的資源可在多處得到
11 301--刪除請求數據
12 302--在其他地址發現了請求數據
13 303--建議客戶訪問其他URL或訪問方式
14 304--客戶端已經執行了GET,但文件為變化***
15 305--請求的資源必須從服務器指定的地址得到
16 306--前一版本HTTP中使用的代碼,現行版本中不再使用
17 307--申明請求的資源臨時性刪除
18 400--錯誤請求,如語法錯誤
19 401--請求授權失敗
20 402--保留有效ChargeTo頭響應
21 403--請求不允許
22 404--沒有發現文件、查詢或URL***
23 405--用戶在Request-Line字段定義的方法不允許
24 406--根據用戶發送的Accept拖,請求資源不可訪問
25 407--類似401,用戶必須首先在代理服務器上得到授權
26 408--客戶端沒有在用戶指定的時間內完成請求
27 409--對當前資源狀態,請求不能完成
28 410--服務器上不再有此資源且無進一步的參考地址
29 411--服務器拒絕用戶定義的Content-Length屬性請求
30 412--一個或多個請求頭字段在當前請求中錯誤
31 413--請求的資源大於服務器允許的大小
32 414--請求的資源URL長於服務器允許的長度
33 415--請求資源不支持請求項目格式
34 416--請求中包含Range請求頭字段,在當前請求資源范圍內沒有range指示值,請求也不包含If-Range請求頭字段
35 417--服務器不滿足請求Expect頭字段指定的期望值,如果是代理服務器,可能是下一級服務器不能滿足請求
36 500--服務器產生內部錯誤***
37 501--服務器不支持請求的函數
38 502--服務器暫時不可用,有時是為了防止發生系統過載
39 503--服務器過載或暫停維修
40 504--關口過載,服務器使用另一個關口或服務來響應用戶,等待時間設定值較長
41 505--服務器不支持或拒絕請求頭中指定的HTTP版本
狀態碼

2.深度優先和廣度優先遍歷算法

  深度優先搜索(DepthFirstSearch):深度優先搜索的主要特征就是,假設一個頂點有不少相鄰頂點,當我們搜索到該頂點,我們對於它的相鄰頂點並不是現在就對所有都進行搜索,而是對一個頂點繼續往后搜索,直到某個頂點,它周圍相鄰頂點都已經被訪問過了,這時它就可以返回,對它原來的那個頂點的其余頂點進行搜索。深度優先搜索的實現可以利用遞歸很簡單的實現。

  廣度優點搜索(BreadthFirstSearch):廣度優先搜索相對於深度優先搜索側重點不一樣,深度優先好比一個人走迷宮,一次只能選定一條路走下去,而廣度優先搜索好比是一次能有任意多個人,一次就走到和一個頂點相連的所有未訪問過的頂點,然后再從這些頂點出發,繼續這個過程。

3.url常見的去重策略

  將訪問過的url保存到數據庫中

  將訪問過的url保存到set中,只需要o(1)的代價就可以查詢url

  url經過MD5等方法哈希后保存到set中

  用bitmap方法,將訪問過的url通過hash函數映射到某一位

  bloomfilter方法對bitmap進行改進,多重hash函數降低沖突

4.爬蟲與反爬蟲

基本概念:  

  爬蟲:自動獲取網站數據的程序,關鍵是批量的獲取

  反爬蟲:使用技術手段防止爬蟲程序的方法

  誤傷:反爬蟲技術將普通用戶識別為爬蟲,如果誤傷過高,效果再好也不能用

  成本:反爬蟲需要的人力和機器成本

  攔截:成功攔截爬蟲,一般攔截率越高,誤傷率越高

反爬蟲的目的:

  應對初級爬蟲(簡單粗暴,不管服務器壓力,容易弄掛網站的)、數據保護、失控的爬蟲(由於某些情況下,忘記或者無法關閉的爬蟲)、商業競爭對手

爬蟲與反爬蟲的對抗過程:

防止爬蟲被網站禁止的策略

  圖片驗證碼、ip訪問頻率限制、user-agent隨機切換

5.提取數據工具

 1 article         選取所有article元素的所有子節點
 2 /article        選取根元素article
 3 article/a       選取所有屬於article的子元素的a元素
 4 //div           選取所有div子元素(不論出現在文檔任何地方)
 5 article//div    選取所有屬於article元素的后代的div元素,不管它出現在article之下的任何位置
 6 //@class        選取所有名為class的屬性
 7             
 8 /article/div[1]         選取屬於article子元素的第一個div元素
 9 /article/div[last()]    選取屬於article子元素的最后一個div元素
10 /article/div[last()-1]  選取屬於article子元素的倒數第二個div元素
11 //div[@lang]            選取所有擁有lang屬性的div元素
12 //div[@lang='eng']      選取所有lang屬性為eng的div元素
13             
14 /div/*                  選取屬於div元素的所有子節點
15 //*                     選取所有元素
16 //div[@*]                選取所有帶屬性的title元素
17 //div/a|//div/p         選取所有div元素的a和p元素
18 //span|//ul             選取文檔中的span和ul元素
19 article/div/p|//span    選取所有屬於article元素的div元素的p元素以及文檔中所有的span元素
xpath選擇器
 1 *                    選擇所有節點
 2 #container           選擇id為container的節點
 3 .container           選擇所有class包含container的節點
 4 li a                 選取所有li下的所有a節點
 5 ul + p               選擇ul后面的第一個p元素
 6 div#container > ul   選取id為container的div的第一個ul子元素
 7 ul ~ p                        選取與ul相鄰的所有p元素
 8 a[title]                      選取所有有title屬性的a元素
 9 a[href="http://jobbole.com"]  選取所有href屬性為jobbole.com值的a元素
10 a[href*="jobbole"]            選取所有href屬性包含jobbole的a元素
11 a[href^="http"]               選取所有href屬性值以http開頭的a元素
12 a[href$=".jpg"]               選取所有href屬性值以.jpg結尾的a元素
13 input[type=radio]:checked     選擇選中的radio的元素
14 div:not(#container)           選取所有id非container的div屬性
15 li:nth-child(3)               選取第三個li元素
16 tr:nth-child(2n)              第偶數個tr
css選擇器

6.調試

scrapy shell  要調試的網址

1 from scrapy.cmdline import execute
2 import sys
3 import os
4 
5 sys.path.append(os.path.dirname(os.path.abspath(__file__)))
6 execute(["scrapy", "crawl", "jobbole"])
main.py

7.安裝開發環境

 1 安裝mysqlclient
 2     windows:pip install mysqlclient
 3     linux:sudo apt-get install libmysqlclient-dev
 4 
 5 安裝虛擬環境
 6     pip install virtualenv
 7     pip install virtualenvwrapper
 8 
 9 在Windows上運行workon
10     pip install virtualenvwrapper-win
11 
12 項目中安裝的包,以及在別的項目中安裝這些包
13     pip freeze > requirements.txt
14     pip install -r requirements.txt
View Code

8.設置用戶代理

 1 from fake_useragent import UserAgent
 2 
 3 
 4 #  隨機更換user-agent
 5 class RandomUserAgentMiddleware(object):
 6     def __init__(self, crawler):
 7         super(RandomUserAgentMiddleware, self).__init__()
 8         self.ua = UserAgent()
 9         # 在settings中配置RANDOM_UA_TYPE,可以獲得相應瀏覽器的user-agent
10         self.ua_type = crawler.settings.get("RANDOM_UA_TYPE", "random")  
11     
12     @classmethod
13     def from_crawler(cls, crawler):
14         return cls(crawler)
15     
16     def process_request(self, request, spider):
17         def get_ua():
18             return getattr(self.ua, self.ua_type)
19         request.headers.setdefault("User_Agent", get_ua())
middlewares.py

9.設置ip代理

 1 import requests
 2 from scrapy.selector import Selector
 3 import MySQLdb
 4 
 5 conn = MySQLdb.connect(host="127.0.0.1", user="root", passwd="ccmldl", db="article_spider", charset="utf8")
 6 cursor = conn.cursor()
 7 
 8 
 9 def crawl_ips():
10     # 爬取西刺的免費ip代理
11     headers = {
12         "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36"
13     }
14     for i in range(1, 3476):
15         re = requests.get("http://www.xicidaili.com/nn/{0}".format(i), headers=headers)
16         selector = Selector(text=re.text)
17         all_trs = selector.css("#ip_list tr")
18         
19         ip_list = []
20         for tr in all_trs[1:]:
21             speed_str = tr.css(".bar::attr(title)").extract_first()
22             if speed_str:
23                 speed = float(speed_str.split("")[0])
24             all_texts = tr.css("td::text").extract()
25             ip = all_texts[0]
26             port = all_texts[1]
27             proxy_type = all_texts[5]
28             ip_list.append((ip, port, proxy_type, speed))
29         
30         for ip_info in ip_list:
31             cursor.execute(
32                 "insert into proxy_ip(ip, port, speed, proxy_type) values ('{0}', '{1}', {2}, '{3}')".format(
33                     ip_info[0], ip_info[1], ip_info[3], ip_info[2]
34                 )
35             )
36             conn.commit()
37 
38 
39 class GetIP(object):
40     def delete_ip(self, ip):
41         # 從數據庫中刪除無效的ip
42         delete_sql = """
43             delete from proxy_ip where ip='{0}'
44         """.format(ip)
45         cursor.execute(delete_sql)
46         conn.commit()
47         return True
48     
49     def judeg_ip(self, ip, port, proxy_type):
50         # 判斷ip是否可用
51         http_url = "http://www.baidu.com"
52         proxy_url = "{0}://{1}:{2}".format(proxy_type.lower(), ip, port)
53         try:
54             proxy_dict = {
55                 "http": proxy_url
56             }
57             response = requests.get(http_url, proxies=proxy_dict)
58             # return True
59         except Exception as e:
60             # print("invalid ip and port")
61             self.delete_ip(ip)
62             return False
63         else:
64             code = response.status_code
65             if code >= 200 and code < 300:
66                 # print("effective ip")
67                 return True
68             else:
69                 # print("invalid ip and port")
70                 self.delete_ip(ip)
71                 return False
72     
73     def get_random_ip(self):
74         # 從數據庫中隨機獲取一個可用的ip
75         random_sql = """
76             select ip, port, proxy_type from proxy_ip order by rand() limit 1
77         """
78         result = cursor.execute(random_sql)
79         for ip_info in cursor.fetchall():
80             ip = ip_info[0]
81             port = ip_info[1]
82             proxy_type = ip_info[2]
83             
84             judge_res = self.judeg_ip(ip, port, proxy_type)
85             if judge_res:
86                 return "{0}://{1}:{2}".format(proxy_type, ip, port)
87             else:
88                 return self.get_random_ip()
89 
90 
91 if __name__ == '__main__':
92     crawl_ips()
93     get_ip = GetIP()
94     print(get_ip.get_random_ip())
crawl_xici_ip.py
1 from tools.crawl_xici_ip import GetIP
2 
3 
4 # 動態設置ip代理
5 class RandomProxyMiddleware(object):
6     def process_request(self, request, spider):
7         get_ip = GetIP()
8         request.meta["proxy"] = get_ip.get_random_ip()
middlewares.py

四、scrapy進階開發

1.selenium(瀏覽器自動化測試框架)

 1 from selenium import webdriver
 2 from scrapy.selector import Selector
 3 import time
 4 
 5 browser = webdriver.Chrome(executable_path=r"D:\谷歌瀏覽器\Google_Chrome_v68.0.3440.106_x64\chromedriver.exe")
 6 browser.get("https://www.taobao.com")
 7 t_selector = Selector(text=browser.page_source)
 8 print(t_selector.css("").extract())
 9     
10 browser.find_element_by_css_selector("").send_keys()
11 browser.find_element_by_css_selector("").click()
12     
13 # 模擬鼠標下滑加載動態頁面
14 browser.execute_script(
15     "window.scrollTo(0, document.body.scrollHeight); 
16     var lenOfPage=document.body.scrollHeight; 
17     return lenOfPage;"
18     )
19     
20 # 設置chromdriver不加載圖片
21 chrome_opt =webdriver.ChromeOptions()
22 prefs = {"profile.managed_default_content_settings.images": 2}
23 chrome_opt.add_experimental_option("prefs": prefs)
24 browser = webdriver.Chrome(
25     executable_path=r"D:\谷歌瀏覽器\Google_Chrome_v68.0.3440.106_x64\chromedriver.exe",
26         chrome_options=chrome_opt
27 )
28 browser.get("https://www.taobao.com")
29     
30 # phantomjs,無界面的瀏覽器,多進程情況下phantomjs性能會下降很嚴重
31 browser = webdriver.Phantomjs(executable_path=r"D:\phantomjs-2.1.1-windows\bin\phantomjs.exe")
32 browser.get("https://www.taobao.com")
33 print(browser.page_source)
34 browser.quit()
簡單使用

2.將selenium集成到scrapy

 1 from selenium import webdriver
 2 from scrapy.xlib.pydispatch import dispatcher
 3 from scrapy import signals
 4         
 5 def __init__(self):
 6     self.browser = webdriver.Chrome(executable_path=r"D:\谷歌瀏覽器\Google_Chrome_v68.0.3440.106_x64\chromedriver.exe")
 7     super(JobboleSpider, self).__init__()
 8     # 信號的作用:當spider關閉的時候我們做什么事情
 9     dispatcher.connect(self.spider_closed, signals.spider_closed)
10         
11 def spider_closed(self, spider):
12     # 當爬蟲退出的時候關閉Chrome
13     print("spider closed")
14     self.browser.quit()
spider
 1 import time
 2 from scrapy.http import HtmlResponse
 3 
 4 
 5 # 通過chromdriver請求動態頁面
 6 def process_request(self, request, spider):
 7     if spider.name == 'jobbole':
 8         spider.browser.get(request.url)
 9         time.sleep(3)
10         print("訪問:{0}".format(request.url))
11         return HtmlResponse(url=spider.browser.current_url, body=spider.browser.page_source, encoding='utf-8', request=request)
middlewares

3.scrapy的暫停與重啟

  job_info需要自己在工程中新建好  

  scrapy crawl jobbole -s JOBDIR=job_info/001

  暫停的信號:Ctrl+c 需要接着原來的數據爬取就執行上面那句話

  如果需要重新爬取:scrapy crawl jobbole -s JOBDIR=job_info/002

4.數據收集(stats collection)

 1 from scrapy.xlib.pydispatch import dispatcher
 2 from scrapy import signals
 3 
 4 
 5 class JobboleSpider(scrapy.Spider):
 6     ...
 7     # 收集伯樂在線所有的404的url以及404頁面數
 8      handle_httpstatus_list = [404] 
 9 
10      def __init__(self):
11          self.fail_urls = []
12 
13     def parse(self, response):
14          if response.status == 404:
15             self.fail_urls.append(response.url)
16             self.crawler.stats.inc_value("failed_url")
17         ...

未完待續...


免責聲明!

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



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