scrapy爬蟲框架

  這是我近期學習的一些內容,可能不只僅侷限於scrapy爬蟲框架,還會有不少知識的擴展。寫的可能不是那麼有條理,想到什麼就寫什麼吧,畢竟也是本身之後深刻學習的基礎,有些知識說的不夠明白歡迎留言,共同窗習!css

1、框架詳解python

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

1.內部各組件的做用linux

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

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

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

  Spiders(爬蟲):爬蟲主要是幹活的,用於從特定網頁中提取本身須要的信息,即所謂的實體(Item)。也能夠從中提取URL,讓scrapy繼續爬取下一個頁面。sql

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

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

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

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

2.Scrapy運行流程

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

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

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

  爬蟲解析Response

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

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

2、爲何使用scrapy?爬蟲能作什麼?

1.Scrapy vs requests+beautifulsoup

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

2.爬蟲能作什麼?

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

3、爬蟲基礎知識

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 一、編譯正則表達式,若是正則表達式自己不合法,會報錯
65 二、用編譯後的正則表達式去匹配對象
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

4、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         ...

未完待續...

相關文章
相關標籤/搜索