python3爬蟲之旅

1.首先我們來完成一個簡單的爬蟲。

import urllib.request
response=urllib.request.urlopen('http://baidu.com')
html=response.read();
print(html)

2.發送請求的同時用post發送表單數據

import urllib
import urllib.request
url="http://baidu.com"
values={"name":"Why",
        "location":"SDU",
        "language":"python"
            }
data=urllib.parse.urlencode(values).encode(encoding='utf_8', errors='strict')#編碼工作
req=urllib.request.Request(url,data)#發送請求同時傳data表單
response=urllib.request.urlopen(req)#接受反饋的信息
the_page=response.read()#讀取反饋的內容
print(the_page)

但是相較於get方式,post方式存在着副作用

3.get方式發送數據請求

import urllib
import urllib.request
url="http://www.imooc.com/"
data = {}  

data['name'] = 'WHY'    
data['location'] = 'SDU'    
data['language'] = 'Python'  
data1=urllib.parse.urlencode(data)
print(data1)
full_url=url+"?"+data1
print(full_url)
response=urllib.request.urlopen(full_url)
the_page=response.read()
print(the_page)

4.設置headers頭的http請求(樓主說這個demo不能工作了)

import urllib    
import urllib2    

url = 'http://www.someserver.com/cgi-bin/register.cgi'  

user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'    
values = {'name' : 'WHY',    
          'location' : 'SDU',    
          'language' : 'Python' }    

headers = { 'User-Agent' : user_agent }    
data = urllib.urlencode(values)    
req = urllib2.Request(url, data, headers)    
response = urllib2.urlopen(req)    
the_page = response.read()   

5.URLError

import urllib
import urllib.request
from _testcapi import exception_print
req=urllib.request.Request("http://www.baibai.com")
print(req)
try:urllib.request.urlopen(req)
except urllib.error.URLError as e:
    print(e.reason)

6.HTTPError
服務器上每一個HTTP 應答對象response包含一個數字”狀態碼”。
有時狀態碼指出服務器無法完成請求。默認的處理器會爲你處理一部分這種應答。
例如:假如response是一個”重定向”,需要客戶端從別的地址獲取文檔,urllib2將爲你處理。
其他不能處理的,urlopen會產生一個HTTPError。
典型的錯誤包含”404”(頁面無法找到),”403”(請求禁止),和”401”(帶驗證請求)。
HTTP狀態碼錶示HTTP協議所返回的響應的狀態。
比如客戶端向服務器發送請求,如果成功地獲得請求的資源,則返回的狀態碼爲200,表示響應成功。
如果請求的資源不存在, 則通常返回404錯誤。

HTTP狀態碼通常分爲5種類型,分別以1~5五個數字開頭,由3位整數組成:

200:請求成功 處理方式:獲得響應的內容,進行處理
201:請求完成,結果是創建了新資源。新創建資源的URI可在響應的實體中得到 處理方式:爬蟲中不會遇到
202:請求被接受,但處理尚未完成 處理方式:阻塞等待
204:服務器端已經實現了請求,但是沒有返回新的信 息。如果客戶是用戶代理,則無須爲此更新自身的文檔視圖。 處理方式:丟棄
300:該狀態碼不被HTTP/1.0的應用程序直接使用, 只是作爲3XX類型迴應的默認解釋。存在多個可用的被請求資源。 處理方式:若程序中能夠處理,則進行進一步處理,如果程序中不能處理,則丟棄
301:請求到的資源都會分配一個永久的URL,這樣就可以在將來通過該URL來訪問此資源 處理方式:重定向到分配的URL
302:請求到的資源在一個不同的URL處臨時保存 處理方式:重定向到臨時的URL
304 請求的資源未更新 處理方式:丟棄
400 非法請求 處理方式:丟棄
401 未授權 處理方式:丟棄
403 禁止 處理方式:丟棄
404 沒有找到 處理方式:丟棄

5XX 迴應代碼以“5”開頭的狀態碼錶示服務器端發現自己出現錯誤,不能繼續執行請求 處理方式:丟棄

HTTPError實例產生後會有一個整型’code’屬性,是服務器發送的相關錯誤號。
Error Codes錯誤碼
因爲默認的處理器處理了重定向(300以外號碼),並且100-299範圍的號碼指示成功,所以你只能看到400-599的錯誤號碼。
BaseHTTPServer.BaseHTTPRequestHandler.response是一個很有用的應答號碼字典,顯示了HTTP協議使用的所有的應答號。
當一個錯誤號產生後,服務器返回一個HTTP錯誤號,和一個錯誤頁面。
你可以使用HTTPError實例作爲頁面返回的應答對象response。
這表示和錯誤屬性一樣,它同樣包含了read,geturl,和info方法。

import random
import socket
import urllib.request
import http.cookiejar

ERROR = {
        '0':'Can not open the url,checck you net',
        '1':'Creat download dir error',
        '2':'The image links is empty',
        '3':'Download faild',
        '4':'Build soup error,the html is empty',
        '5':'Can not save the image to your disk',
    }

class BrowserBase(object): 

    def __init__(self):
        socket.setdefaulttimeout(20)

    def speak(self,name,content):
        print('[%s]%s' %(name,content))

    def openurl(self,url):
        cookie_support= urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar())
        self.opener = urllib.request.build_opener(cookie_support,urllib.request.HTTPHandler)
        urllib.request.install_opener(self.opener)
        user_agents = [
                    'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11',
                    'Opera/9.25 (Windows NT 5.1; U; en)',
                    'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
                    'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)',
                    'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
                    'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
                    "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7",
                    "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0 ",

                    ] 

        agent = random.choice(user_agents)
        self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://www.google.com')]
        try:
            res = self.opener.open(url)
            print(res.read())
        except Exception as e:
            self.speak(str(e)+url)
            raise Exception
        else:
            return res

if __name__=='__main__':
    splider=BrowserBase()
    splider.openurl('http://blog.csdn.net/v_JULY_v/archive/2010/11/27/6039896.aspx') 

由於有些網頁是禁止爬蟲的,所以要以正常的形式來獲取,上述代碼就是一個正規訪問的方式
7.wrapping


import urllib.request
req=urllib.request.Request('http://baibai.com/')
try:response=urllib.request.urlopen(req)
except urllib.error.URLError as e:
        if hasattr(e, 'code'):
            print("The server couldn't fulfill the request")
            print("Error code",e.code)
        elif hasattr(e, 'reason'):
            print('we failed to reach a server.')
            print('Reason',e.reason)
        else:
            print('No exception was raised')

8.geturl()和getInfo()


import urllib.request
req=urllib.request.Request('http://baidu.com')
response=urllib.request.urlopen(req)
print("real url"+response.geturl())
print(response.info())

運行結果:
real urlhttp://baidu.com
Date: Sat, 15 Oct 2016 15:12:06 GMT
Server: Apache
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: “51-47cf7e6ee8400”
Accept-Ranges: bytes
Content-Length: 81
Cache-Control: max-age=86400
Expires: Sun, 16 Oct 2016 15:12:06 GMT
Connection: Close
Content-Type: text/html

9.opener和handler

# -*- coding: utf-8 -*-  
import urllib.request  

# 創建一個密碼管理者  
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()  

# 添加用戶名和密碼  

top_level_url = "http://example.com/foo/"  

# 如果知道 realm, 我們可以使用他代替 ``None``.  
# password_mgr.add_password(None, top_level_url, username, password)  
password_mgr.add_password(None, top_level_url,'why', '1223')  

# 創建了一個新的handler  
handler = urllib.request.HTTPBasicAuthHandler(password_mgr)  

# 創建 "opener" (OpenerDirector 實例)  
opener = urllib.request.build_opener(handler)  

a_url = 'http://www.baidu.com/'  

# 使用 opener 獲取一個URL  
opener.open(a_url)  

# 安裝 opener.  
# 現在所有調用 urllib.request.urlopen 將用我們的 opener.  
urllib.request.install_opener(opener)  

注意:以上的例子我們僅僅提供我們的HHTPBasicAuthHandler給build_opener。
默認的openers有正常狀況的handlers:ProxyHandler,UnknownHandler,HTTPHandler,HTTPDefaultErrorHandler, HTTPRedirectHandler,FTPHandler, FileHandler, HTTPErrorProcessor。
代碼中的top_level_url 實際上可以是完整URL(包含”http:”,以及主機名及可選的端口號)。
例如:http://example.com/
也可以是一個“authority”(即主機名和可選的包含端口號)。
例如:“example.com” or “example.com:8080”。
後者包含了端口號。

10.urllib細節

proxy_handler=urllib.request.ProxyHandler({“http”:”http://some-proxy.com:8080})
opener=urllib.request.build_opener(proxy_handler)
urllib.request.install_opener(opener)
request=urllib.request.Request(“”)
request.add_header(“User_Agent”,’fake-client’)
cookie=http.cookiejar.CookieJar()
opener=urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))
for item in cookie:
print ‘Name=’+item.name
print ‘Value’+item.value
httpHandler=urllib.request.HTTPHandler(debuglevel=1)
httpsHandler=urllib.request.HTTPSHandler(debuglevel=1)
opener=urllib.request.build_opener(httpHandler,httpsHandler)
Content-Type : 在使用 REST 接口時,服務器會檢查該值,用來確定 HTTP Body 中的內容該怎樣解析。常見的取值有:
application/xml : 在 XML RPC,如 RESTful/SOAP 調用時使用
application/json : 在 JSON RPC 調用時使用
application/x-www-form-urlencoded : 瀏覽器提交 Web 表單時使用
在使用服務器提供的 RESTful 或 SOAP 服務時, Content-Type 設置錯誤會導致服務器拒絕服務
判斷重定向:response.geturl==mu_url

11.一個簡單的百度小爬蟲

import urllib.request as request  
import urllib.parse as parse  
import string  
def baidu_tieba(url, begin_page, end_page):  
    for i in range(begin_page, end_page + 1):  
        sName = 'd:/test/'+str(i).zfill(5)+'.html'  
        print('正在下載第'+str(i)+'個頁面, 並保存爲'+sName)  
        m = request.urlopen(url+str(i)).read()  
        with open(sName,'wb') as file:  
            file.write(m)  
        file.close()  
if __name__ == "__main__":  
    url = "http://tieba.baidu.com/p/"  
    begin_page = 1  
    end_page = 3  
    baidu_tieba(url, begin_page, end_page)  

12.介紹re模塊
re模塊的操作步驟:
step1將正則表達式轉化爲Pattern實例
step2用Pattern實例進行匹配,得到Match實例
step3通過Match實例對得到的結果進行操作處理

import re
#將正則表達式編譯成爲Pattren對象,注意hello前面的r的意思是“原生字符串"
pattern=re.compile(r'hello')
#使用Pattren匹配文本,獲得匹配結果,無法匹配時將返回none
match1=pattern.match('hello world!')
match2=pattern.match('helloo world!')
match3=pattern.match("helllo world!")

#如果match1匹配成功
if match1:
    print(match1.group())
else:
    print("match1匹配失敗")
if match2:
    print(match2.group())
else:
     print("match2匹配失敗")
if match3:
    print(match3.group())
else:
     print("match3匹配失敗")

Match對象是一次匹配的結果,包含了很多關於此次匹配的信息,可以使用Match提供的可讀屬性或方法來獲取這些信息。
屬性:
string: 匹配時使用的文本。
re: 匹配時使用的Pattern對象。
pos: 文本中正則表達式開始搜索的索引。值與Pattern.match()和Pattern.seach()方法的同名參數相同。
endpos: 文本中正則表達式結束搜索的索引。值與Pattern.match()和Pattern.seach()方法的同名參數相同。
lastindex: 最後一個被捕獲的分組在文本中的索引。如果沒有被捕獲的分組,將爲None。
lastgroup: 最後一個被捕獲的分組的別名。如果這個分組沒有別名或者沒有被捕獲的分組,將爲None。
方法:
group([group1, …]):
獲得一個或多個分組截獲的字符串;指定多個參數時將以元組形式返回。group1可以使用編號也可以使用別名;編號0代表整個匹配的子串;不填寫參數時,返回group(0);沒有截獲字符串的組返回None;截獲了多次的組返回最後一次截獲的子串。
groups([default]):
以元組形式返回全部分組截獲的字符串。相當於調用group(1,2,…last)。default表示沒有截獲字符串的組以這個值替代,默認爲None。
groupdict([default]):
返回以有別名的組的別名爲鍵、以該組截獲的子串爲值的字典,沒有別名的組不包含在內。default含義同上。
start([group]):
返回指定的組截獲的子串在string中的起始索引(子串第一個字符的索引)。group默認值爲0。
end([group]):
返回指定的組截獲的子串在string中的結束索引(子串最後一個字符的索引+1)。group默認值爲0。
span([group]):
返回(start(group), end(group))。
expand(template):
將匹配到的分組代入template中然後返回。template中可以使用\id或\g、\g引用分組,但不能使用編號0。\id與\g是等價的;但\10將被認爲是第10個分組,如果你想表達\1之後是字符’0’,只能使用\g<1>0。
下面來用一個py實例輸出所有的內容加深理

import re  
# 匹配如下內容:單詞+空格+單詞+任意字符  
m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')  

print "m.string:", m.string  
print "m.re:", m.re  
print "m.pos:", m.pos  
print "m.endpos:", m.endpos  
print "m.lastindex:", m.lastindex  
print "m.lastgroup:", m.lastgroup  

print "m.group():", m.group()  
print "m.group(1,2):", m.group(1, 2)  
print "m.groups():", m.groups()  
print "m.groupdict():", m.groupdict()  
print "m.start(2):", m.start(2)  
print "m.end(2):", m.end(2)  
print "m.span(2):", m.span(2)  
print r"m.expand(r'\g<2> \g<1>\g<3>'):", m.expand(r'\2 \1\3')  

### output ###  
# m.string: hello world!  
# m.re: <_sre.SRE_Pattern object at 0x016E1A38>  
# m.pos: 0  
# m.endpos: 12  
# m.lastindex: 3  
# m.lastgroup: sign  
# m.group(1,2): ('hello', 'world')  
# m.groups(): ('hello', 'world', '!')  
# m.groupdict(): {'sign': '!'}  
# m.start(2): 6  
# m.end(2): 11  
# m.span(2): (6, 11)  
# m.expand(r'\2 \1\3'): world hello!  

下面重點介紹一下pattern實例及方法
match(string[, pos[, endpos]]) | re.match(pattern, string[, flags])
search(string[, pos[, endpos]]) | re.search(pattern, string[, flags])
split(string[, maxsplit]) | re.split(pattern, string[, maxsplit])
findall(string[, pos[, endpos]]) | re.findall(pattern, string[, flags])
finditer(string[, pos[, endpos]]) | re.finditer(pattern, string[, flags])
sub(repl, string[, count]) | re.sub(pattern, repl, string[, count])
subn(repl, string[, count]) |re.sub(pattern, repl, string[, count])

12.糗事百科的爬蟲代碼

# -*- coding: utf-8 -*-    

import urllib.request    
import urllib    
import re      
import time  
import threading
import _thread  


#----------- 加載處理糗事百科 -----------    
class Spider_Model:    

    def __init__(self):    
        self.page = 1    
        self.pages = []    
        self.enable = False    

    # 將所有的段子都扣出來,添加到列表中並且返回列表    
    def GetPage(self,page):    
        myUrl = "http://m.qiushibaike.com/hot/page/" + page    
        user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'   
        headers = { 'User-Agent' : user_agent }   
        req = urllib.request.Request(myUrl, headers = headers)   
        myResponse = urllib.request.urlopen(req)  
        myPage = myResponse.read()   
        #encode的作用是將unicode編碼轉換成其他編碼的字符串    
        #decode的作用是將其他編碼的字符串轉換成unicode編碼    
        unicodePage = myPage.decode("utf-8")    

        # 找出所有class="content"的div標記    
        #re.S是任意匹配模式,也就是.可以匹配換行符    
       # myItems = re.findall('<div.*?class="content".*?title="(.*?)">(.*?)</div>',unicodePage,re.S)
        myItems = re.findall('<div.*?class="(.*?)">.*?<span>(.*?)</span></div>',unicodePage,re.S)


        items = []    
        for item in myItems:    
            # item 中第一個是div的標題,也就是時間    
            # item 中第二個是div的內容,也就是內容   
            print(item[1]) 
            items.append([item[0].replace("\n",""),item[1].replace("\n","")])    
        return items    

    # 用於加載新的段子    
    def LoadPage(self):    
        # 如果用戶未輸入quit則一直運行    
        while self.enable:    
            # 如果pages數組中的內容小於2個    
            if len(self.pages) < 2:    
                try:    
                    # 獲取新的頁面中的段子們    
                    myPage = self.GetPage(str(self.page))    
                    self.page += 1    
                    self.pages.append(myPage)    
                except:    
                    print( '無法鏈接糗事百科!' )   
            else:    
                time.sleep(1)    

    def ShowPage(self,nowPage,page):    
        for items in nowPage:    
            print( u'第%d頁' % page , items[0]  , items[1]  )  
            myInput = input()    
            if myInput == "quit":    
                self.enable = False    
                break    

    def Start(self):    
        self.enable = True    
        page = self.page    

        print (u'正在加載中請稍候......')    

        # 新建一個線程在後臺加載段子並存儲    
        _thread.start_new_thread(self.LoadPage,()) 
        #thread.start   

        #----------- 加載處理糗事百科 -----------    
        while self.enable:    
            # 如果self的page數組中存有元素    
            if self.pages:    
                nowPage = self.pages[0]    
                del self.pages[0]    
                self.ShowPage(nowPage,page)    
                page += 1    


#----------- 程序的入口處 -----------    



print (u'請按下回車瀏覽今日的糗百內容:')    
input(' ')    
myModel = Spider_Model()    
myModel.Start()    

上面的代碼可以運行,但是不能爬取內容,原因是正則表達式有問題,樓主現在還不是很明白,以後熟悉了再更新。

13.Scrapy框架學習
由於windows系統還不支持python3.x+Scrapy框架,所以這裏需要我使用了python2.7+Scrapy版本進行程序代碼的編譯調試。
Scrapy入門教程可以在網站上找到

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章