1. 程式人生 > >Python爬蟲實習筆記 | Week2 Python正則和BeautifulSoup學習與試煉

Python爬蟲實習筆記 | Week2 Python正則和BeautifulSoup學習與試煉

2018/10/22 23
1.所思所想:今天狀態一直不佳,一是因為自己晚上晚睡,睡眠不足,比較睏倦;二是自己爬蟲基礎還不牢靠,還需要努力學習,比較慚愧;三是之前的專案,組長趙某乃不值得信賴之人物,使得自己多生煩憂,《MySQL》也上交了。。還好下午把學長寫的爬蟲跑通了,今天下午把程式碼理解一遍,然後自己跑一個城市。

2.工作:
(1).《Python爬蟲專案實戰》中的123Chapter,感覺自己對爬蟲理解也更加深刻,《數學之美》在“道”上給了我很多啟發。對爬蟲基礎部分我也做了總結,之後寫在部落格上。
(2).下午我把Python的正則部分看了一下,只能說掌握了核心要點,但已經足夠處理專案我想,之後有時間我應該系統學習一下。
(3).Beautifulsoup很強大,但和CSS選擇器結合,以及它本身的特性不是很瞭解,昨天看了一下,不是能透徹理解,明天還要繼續看。

Python3
用requests模組抓取網頁資源
1.使用GET請求:
import requests

r = request.get('http://www.zhihu.com')
print(r.content)
-------------------------------
在連結後新增字尾的請求
import requests

payload = { 'Keywords' : 'blog:qiyeboy', 'pageindex' : 1 }
r = requests.get('http://www.baidu.com', params = payload)

2.使用POST請求
import requests

postdata = { 'key' : 'data' }
r = request.post('http://www.zhihu.com', data=postdata)

print( r.content )  #以位元組的形式返回內容
print( r.text ) #以文字的形式返回內容
print( r.encoding ) #返回網頁的編碼格式

3.編碼設定:
為了防止Requests猜測編碼錯誤,方法1是自行設定編碼格式:r.encoding = 'utf-8',方法2是使用非常優秀的字串/檔案編碼檢測模組chardet,使用chardet.detect()返回字典,其中condidence是檢測精度,encoding是編碼形式。
import requests
import chardet

r = requests.get('http://www.baidu.com')
r.encoding = chardet.detect(r.content)['encoding']
print( r.text )

4.請求頭headers處理
import requests

user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Window NT)'
headers = { 'User-Agent' : user_agent }
r = requests.get('http://www.baidu.com', headers = headers)
print( r.content )

5.響應碼code和響應頭headers處理
import requests

r = requests.get('http://www.baidu.com')
if r.status_code == requests.codes.ok:
    print( r.status_code )
    print( r.headers )
    print( r.headers.get('content-type') )

6.Cookie處理
import requests

user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = {'User-Agent' : user_agent}
r = requests.get('http://www.baidu.com', headers = headers)
#遍歷出所有的cookie值欄位的值
for cookie in r.cookies.keys():
    print( cookie + ':' + r.cookies.get(cookie) )

7.重定向
處理重定向只是需要設定一下allow_redirects欄位即可,例如 r = requests.get('http://www.baidu.com', allow_redirects=True)。將allow_redirects設定為True,則是允許重定向;設定為False,則禁止重定向。

import requests

r = requests.get('http://www.github.com')
print( r.url )
print( r.status_code )
print( r.history )

8.超時設定
requests.get('http://www.github.com', timeout = 2)

9.代理設定
import requests

proxies = {
    'http' : 'http://0.10.1.10:3128',
    'https' : 'http://10.10.1.10:1080'
}
requests.get('http:example.org', proxies = proxies)

主題2:正則表示式
正則表示式是由普通字元(例如字元a到z)以及特殊字元(稱為“元字元”)組成的文字模式。模式用於在搜尋文字時要匹配一個或多個字串。

(1).常見的元字元如下:
.    匹配除換行符以外的任意字元
\b    匹配單詞的開始和結束
\d    匹配數字
\w    匹配字母,數字,下劃線和漢字
\s    匹配任意空白符,包括空格,製表符(Tab),換行符,中文全形空格等
^    匹配字串的開始
$    匹配字串的結束

Examples:
1.匹配所有以s開頭的單詞    \bs\w*\b
2.匹配以s開頭後跟數字的字串(比如:s100)   ^s\d*$
3.匹配網址(比如:www.google.com)   www\.google\.com

(2).重複:
*    重複零次或更多次
+    重複一次或更多次
?    重複零次或一次
{n}    重複n次
{n,}    重複n次或者更多次
{n,m}    重複n到m次

Examples:
1.匹配hello後面跟1個或更多數字    hello\d+
2.匹配5到12個數字的字串    ^\d{5,12}$
3.匹配we後面跟0個或者1個數字    we\d?

(3).字元集合:
[...], 利用自定義的字元集合,可以匹配幾個字元中的單個字元,以及字元範圍內的單個字元。

Examples:
1.匹配a,b,c,d,e中的單個字元    [abcde]
2.匹配0到8裡的數字        [0-8]
3.匹配所有的字母和數字        [0-9a-zA-Z]

(4).分支條件
正則表示式裡的分支條件指的是有幾種匹配規則,如果滿足其中任意一種規則都應該當成匹配,具體方法是用“|”把不同的規則分隔開。

Examples:
1.匹配電話號碼,一種是3位區號,8位本地號,比如:010-11223344;另一種是4位區號,7位本地號,比如:0321-1234567        0\d{2}-\d{8}|0\d{3}-\d{7}
2.匹配IP地址        ((25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.){3}((25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d))

(5).反義
有時候需要查詢除某一類字元集合以外的字元,這時就需要使用反義。
常用的反義:
\W    匹配任意不是字母,數字,下劃線,漢字的字元
\S    匹配任意不是空白符的字元
\D    匹配任意非數字的字元
\B    匹配不是單詞開頭或結束的位置
[^a]    匹配除了a以外的任意字元
[^abcde]匹配除了a,b,c,d,e以外的任意字元
[^(123|abc)]    匹配除了1,2,3 或者a,b,c這幾個字元以外的任意字元

(6).後向引用

(7).零寬斷言

(8).貪婪與懶惰
當正則表示式中包含能接受重複的限定符時,通常的行為是匹配儘可能多的字元,這就是貪婪模式。
以表示式a\w+b為例,如果搜尋a12b34b,最後會匹配a12b34b,而不是a12b。如果想匹配a12b,應使用“?”來開啟懶惰模式。
*?    重複任意次,但儘可能少重複
+?    重複1次或更多次,但儘可能少重複
??    重複0次或1次,但僅可能少重複
{n,m}    重複n到m次,但儘可能少重複
{n,}?    重複n次以上,但儘可能少重複
    
(9).處理選項
。。。

Python與正則
對不同的語言,對正則表示式的語法絕大部分都是支援的,但是還是有略微不同,每種語言都有一些獨特的匹配規則。
Python的匹配規則:
\A    僅匹配字串開頭
\Z    僅匹配字串末尾
(?P<name>)    分組,除了原有編號外再指定一個額外的別名
(?P=name)    引用別名為<name>的分組匹配到的字串


Python 通過re模組提供對正則表示式的支援
使用re的一般步驟是:
(1).先將正則表示式的字串形式編譯成Pattern例項;
(2).然後使用Pattern例項處理文字並獲得匹配結果;
(3).最後使用Match例項獲得資訊。

1.re.compile(string, [flags]) #它將一個正則表示式轉化為Pattern匹配物件
Example: pattern = re.compile(r'\d+')

2.re.match(pattern, string, [flags]) <等價於 pattern.match(string, [flags])>#該函式是從輸入引數string(匹配的字串)的開頭開始,嘗試匹配pattern,一直向後匹配,如果遇到無法匹配的字元或者已經到達string的末尾,立即返回None,反之,獲得匹配的結果。
Example:
# 1.將正則表示式編譯成pattern物件
pattern = re.compile(r'\d+')
# 2.使用re.match匹配文字,獲得匹配結果,無法匹配時將返回None
result1 = re.match(pattern, '192abc')
if result1:
    print(result1.group())
else:
    print('匹配失敗1')
result2 = re.match(pattern, 'abc192')
if result2:
    print(result2.group())
else:
    print('匹配失敗2')
Result:
192
匹配失敗2

3.re.search(pattern, string, [flags]) <等價於 pattern.search(string, [flags])> #search方法和match方法很類似,只是search()會掃描整個string查詢匹配,而不是從string的開始位置進行匹配
Example:
# search方法的返回物件在方法和屬性上和match是一致的
import re

# 將正則表示式編譯成pattern物件
pattern = re.compile(r'\d+')
result1 = re.search(pattern, 'abc192edf333')
if result1:
    print(result1.group())
else:
    print('匹配失敗1')
Result:
192

4.re.split(pattern, string, [flags])  # split 能夠匹配的子串將string分割後返回列表
Example:
import re

pattern = re.compile(r'\d+')
print(re.split(pattern, 'a1b2c3d4'))
Result:
['a', 'b', 'c', 'd', '']

5.re.findall(pattern, string, [flags])  # findall 搜尋整個string,以列表的形式返回能匹配的全部子串
Example:
import re

pattern = re.compile(r'\d+')
print(re.findall(pattern, 'a1b2c3d4'))
Result:
['1', '2', '3', '4']

6.re.finditer(pattern, string, [flags]) # finditer 搜尋整個string,以迭代器形式返回能匹配的全部Match物件
Example:
import re

pattern = re.compile(r'\d+')
matchiter = re.finditer(pattern, 'a1b2c3d4')
for match in matchiter:
    print(match.group())
Result:
1
2
3
4
7.re.sub(pattern, repl, string [,count])
Example:
import re

p = re.compile(r'(?P<word1>\w+) (?P<word2>\w+)')  #使用名稱引用
s = 'I say, hello, world!'
print(re.sub(p, '111', 'asd f?fdf ss!fdas [email protected]'))
Result:
[email protected]

8.re.subn(pattern, repl, string [,count]) #返回( sub(repl, string[, count]), 替換次數 )

主題3:強大的BeautifulSoup
1.簡要介紹:BeautifulSoup是一個可以從HTML或XML檔案中提取資料的Python庫,它能夠通過你喜歡的轉換器實現慣用的文件導航,查詢,修改文件的方式。

2.Beautiful Soup的安裝
方法1: pip install bs4
方法2:在Pycharm中,可以在File -> Settings -> Project Interpreter -> 右側有個加號按鈕 -> 在彈出的視窗搜尋bs4並安裝。

3.BeautifulSoup的使用
[0].bs4庫的匯入
from bs4 import BeautifulSoup
[1].建立BeautifulSoup物件
以下'lxml'是手動指定的解析器。如果省略,BeautifulSoup一般會選擇最合適的解析器來解析這段文件,如果手動指定,那麼BeautifulSoup會選擇指定的解析器來解析文件。
方式1. 直接通過字串建立
soup = BeautifulSoup(html_str, 'lxml', from_encoding = 'utf-8')

    Example:
    from bs4 import BeautifulSoup
    import requests
    import chardet

    url = 'http://www.baidu.com'
    response = requests.get(url)
    response.encoding = chardet.detect(response.content)['encoding']
    text = response.text

    soup = BeautifulSoup(text, 'lxml')
    print(soup.prettify())
方式2. 通過html檔案來建立
    from bs4 import BeautifulSoup
    import requests
    import chardet


    url = 'http://www.baidu.com'
    response = requests.get(url)
    response.encoding = chardet.detect(response.content)['encoding']
    text = response.text

    with open('hell', 'w') as fout:
        fout.write(text)

    soup = BeautifulSoup(open('hell'), 'lxml')
    print(soup.prettify())
[2].物件種類
BeautifulSoup將複雜HTML文件轉換成一個複雜的樹形結構,每個節點都是Python物件,所有物件可以歸納為4種:
a. Tag
b. NavigableString
c. BeautifulSoup
d. Comment

1). Tag
Tag物件與XML或HTML原生文件中的Tag相同,通俗點說就是標記。標記及其裡面的內容稱為Tag物件。
import requests
import chardet

url = 'http://www.baidu.com'
response = requests.get(url)
response.encoding = chardet.detect(response.content)['encoding']
text = response.text

with open('hell', 'w') as fout:
    fout.write(text)

soup = BeautifulSoup(open('hell'), 'lxml')
# print(soup.prettify())
print(soup.title)
print(soup.a)

Result:
<title>百度一下,你就知道</title>
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新聞</a>

從栗子中可以看出,利用soup加標記名就可以很容易這些標記的內容,比之前講的正則表示式簡單多了。不過利用這種方式,查詢的時所有內容中第一個符合要求的標記。

Tag中有兩個最重要的屬性:name和atrributes。
.name
每個Tag都有自己的名字,通過.name來獲取
print( soup.name )
print( soup.title.name )

Result:
[document]
title
soup物件本身比較特殊,它的name為[document],對於其他內部標記,輸出的值便是標記本身的名稱。
Tag不僅可以獲取name,還可以修改name,改變之後將影響所有通過當前BeautifulSoup物件生成的HTML文件。
soup.title.name = 'mytitle'
print( soup.title )
print( soup.mytitle )

Result:
None
<mytitle>百度一下,你就知道</mytitle>

屬性:
Tag的屬性的操作方法與字典相同,直接獲取; 也可以直接‘點’取屬性,.attrs,用於獲取Tag中的所有屬性。
print( soup.a['href'] )
print( soup.a.attrs['href'] )


2).NavigableString
我們已經得到了標記的內容,要想獲取標記內部的文字怎麼辦呢?需要用到.string
Example:
print( soup.title.string )
print( type(soup.title.string) )

Result:
百度一下,你就知道
<class 'bs4.element.NavigableString'>

注:BeautifulSoup用NavigableString類來包裝Tag中的字串,一個Navigable字串與Python中的Unicode字串相同。

3).BeautifulSoup物件表示的是一個文件的全部內容。大部分時候,可以把它當作Tag物件,是一個特殊的Tag物件,因為BeautifulSoup物件並不是真正的HTML或XML的標記,所以它沒有name和attribute屬性。但為了將BeautifulSoup物件標準化為Tag物件,實現介面的統一,我們依然可以分別獲取它的name和attribute屬性。

4).Comment
Tag, NavigableString, BeautifulSoup幾乎覆蓋了HTML和XML中的所有內容,但是還是有些特殊物件。容易讓人擔心的內容是文件的註釋部分:如果不清楚這個標記.string的情況下,可能會造成資料提取混亂。

[3].遍歷文件樹
×××重中之重
[4].搜尋文件樹
BeautifulSoup定義了很多搜尋方法,這裡著重介紹find_all()方法,其他方法的引數和用法類似,請大家舉一反三。
find_all方法,用於搜尋當前Tag的所有Tag子節點,並判斷是否符合過濾器的條件,函式原型如下:
find_all(name, attrs, recursive, text, **kwargs)
1).name引數
name引數可以查詢所有名字為name的標記,字串物件會被自動忽略掉。name引數值可以是字串,正則表示式,列表,True和方法。
字串:print( soup.find_all('a') )
正則表示式:print( soup.find_all(re.compile('^b')) )
列表:print( soup.find_all(['a', 'b']) )
True: print( soup.find_all(True) )
2).kwargs引數
kwargs引數在Python裡表示為keyword引數。如果一個指定名字的引數不是搜尋內建的引數名

[5].CSS選擇器
我們也可以利用CSS選擇器來篩選元素,在寫CSS時,標記名前不加任何修飾,類名前加‘.’, id前加‘#’。用到的方法是soup.select(),返回型別時list。
1).通過標記名稱進行查詢
# 直接查詢title標記
print( soup.select('title') )

# 逐層查詢title標記
print( soup.select('html head title') )

# 查詢直接子節點
# 查詢head下的title標記
print( soup.select('head > title') )
# 查詢p下的id="link1"的標記
print( soup.select('p > #link1') )

#查詢兄弟節點
#查詢id='link1'之後class=sister的所有兄弟標記
print( soup.select('#link1 ~ .sister') )
#查詢緊跟id = 'link1' 之後class=sister的子標記
print( soup.select('#link1 + .sister') )

2).通過CSS的類名查詢
print( soup.select('.sister') )
print( soup.select('[class~=sister]') )

3).通過tag的id查詢
print( soup.select('#link1') )
print( soup.select('a#link2') )

4).通過是否存在某個屬性來查詢
print( soup.select('a[href]') )

5).通過屬性值來查詢
Tag[attribute]        用於選取帶有指定屬性的元素
Tag[attribute=value]    用於選取帶有指定屬性和值的元素
Tag[attribute~=value]    用於選取屬性值中包含指定詞彙的元素
Tag[attribute|=value]    用於選取帶有以指定值開頭的屬性值的元素,該值必須是整個單詞
Tag[attribute^=value]    匹配屬性值以指定值開頭的每個元素
Tag[attribute$=value]    匹配屬性值以指定值結尾的每個元素
Tag[attribute*=value]    匹配屬性值中包含指定值的每個元素    


2018/10/24
1.所思所想:今天上午將BeautigulSoup知識點整理了下,感覺對這一塊也更加理解。對於規範化的資料文字,比如html文字,應該使用BeautifulSoup來處理;而非規範化的資料,則只能用Python中的正則了。中午公司福利,吃得很嗨,感覺自己做人做事方面仍然有很多改進空間。下午,又開始跑哈爾濱市,開始明白get和post的不同。自己需要增長的方面還有非常多。

2.工作:
(1).整理BeautifulSoup知識點,對BeautifulSoup更加理解,靈活運用;
(2).深刻理解get和post的不同。如何區別get和post請求? 按F12鍵->網路,然後在分頁面切換頁面,會看到型別為html的檔案的請求的型別; 也可以直接檢視訊息頭中的訊息頭。第一,get請求在分頁中的網址往往是變化的,而post請求在分頁中的地址往往是不變的,但並不絕對,比如哈爾濱環保局的行政處罰頁面。第二,post請求在爬取資料時,還要往requests.post()中新增引數data = postdata,postdata資料可能是變化的,也可能是不變的,這個資料可以在 F12 -> 引數中檢視。第三,get請求的爬取網頁可以直接在網址中檢視,而post請求的爬取網頁必須在訊息頭中檢視。

2018/10/25
1.所思所想:昨晚就聽馬亮學長說第二天有顯示器啥的搬過來,還是挺激動的,因為我這兒筆記本顯示屏比較小,上午幫忙搬顯示屏,然後mysql遇到問題,很頭疼,網上也沒找到好的辦法,只好解除安裝重灌。上午,然後是我們Python爬蟲後端的開會,這是我第一次參與開會,主要就是講了目前仍存在的問題以及後期的改進,我意識到,自己仍存在很多問題需要改進。下午跑了防城港的環評,成功惹!!!第一次跑通,哭泣。

2.工作:
(1).今天把整個專案程式碼不熟悉的部分又看了一下,感覺自己的python還需要提升。對爬蟲的大部分概念我應該已經掌握,但技術細節仍然有很多需要彌補的地方。現在主要還是將自己需要做好的做好。
(2).開會。思考如何設計更好的指令碼框架。

2018/10/26
1.所思所想:昨晚睡了晚了些,今早困了些。然後,最大的感受是,要多和別人討論,多吸納別人意見,有問題多交流。

2.工作:
(1).今天上午學習了beautifulSoup新知識,對於網頁處理有了更多的工具;另外,對於自動更新思考了下,寫了簡單的指令碼,覺得還行吧.
(2).下午,主要就是處理時間更新所遇到的問題,對問題解決的越深入,越能感覺到自己仍有較大提升空間;