1. 程式人生 > >python全站爬蟲知識點詳解

python全站爬蟲知識點詳解

1 最簡單的單頁面抓取
思路:
獲取頁面所有url
對獲取的所有url進行分類
A 獲取屬於本域名下的url
B 獲取屬於其他url

程式碼說明:

import urllib
from bs4 import BeautifulSoup
import re

def get_all_url(url):
   urls = []
   web = urllib.urlopen(url)#使用urllib模組的urlopen函式開啟url,複製給web
soup =BeautifulSoup(web.read())#將web內容轉化為beautigulsoup格式的資料。
#通過正則過濾合理的url(針對與freebuf.com來講) tags_a =soup.findAll(name='a',attrs={'href':re.compile("^https?://")}) #soup.findall函式的運用,配合正則表示式來過濾url try : for tag_a in tags_a: #re:^ 表示匹配字串開頭例如,^ abc 匹配 abc ? 表示匹配前一個字串0次或1次,例如 abc? 匹配 ab 和 abc #return urls except: pass return
urls #得到所有freebuf.com下的url def get_local_urls(url): local_urls = [] urls = get_all_url(url) for _url in urls: ret = _url if 'freebuf.com' in ret.replace('//','').split('/')[0]: local_urls.append(_url) return local_urls #if 'freebuf.com' in ret.replace('//','').split('/')[0]:這個if語句不是很明白,通過split()函式,把域名分割,獲取分割後組成的類表的第一個字串。但是在分割前為什麼要把//替換成空格???
#得到所有的不是freebuf.com域名的url def get_remote_urls(url): remote_urls = [] urls = get_all_url(url) for _url in urls: ret = _url if "freebuf.com" not in ret.replace('//','').split('/')[0]: remote_urls.append(_url) return remote_urls #主函式 def __main__(): url = 'http://freebuf.com/' rurls = get_remote_urls(url) print "--------------------remote urls-----------------------" for ret in rurls: print ret print "---------------------localurls-----------------------" lurls = get_local_urls(url) for ret in lurls: print ret if __name__ == '__main__': __main__()

上面是單獨對一個頁面抓取。如果對整個站點抓取的話,還設計到url的處理,用作者的原話:
我們可以把整站當成一個錯綜複雜的圖結構,有一些演算法基礎的讀者都會知道圖的簡單遍歷方法:dfs和bfs(深度優先和廣度優先)。如果這裡讀者有問題的話建議先去學習一下這兩種演算法。大體的演算法結構我們清楚了,但是在實現中我們顯然需要特殊處理url,需要可以區分當前目標站點域名下的網站和其他域名的網站,除此之外,在href的值中經常會出現相對url,這裡也要特別處理。
下面是程式碼:

import urllib
from bs4 import BeautifulSoup
import urlparse
import time
import urllib2
 #urllib 和 urllib2的區別http://blog.csdn.net/dolphin_h/article/details/45296353

url = "http://xxxx.xx/"
domain = "xxxx.xx"
deep = 0
tmp = ""
sites = set()
visited = set()
#local = set()
#集合:
python的set和其他語言類似, 是一個無序不重複元素集, 基本功能包括關係測試和消除重複元素. 集合物件還支援union(聯合), intersection(交), difference(差)和sysmmetric difference(對稱差集)等數學運算.  

def get_local_pages(url,domain):
   global deep
   global sites
   global tmp
   repeat_time = 0
   pages = set()


    #防止url讀取卡住
   while True:
       try:
           print "Ready to Open the web!"
           time.sleep(1)
           #time.sleep()函式,
           Python time sleep() 函式推遲呼叫執行緒的執行,可通過引數secs指秒數,表示程序掛起的時間

           print "Opening the web", url
           web = urllib2.urlopen(url=url,timeout=3)
           print "Success to Open the web"
           break
       except:
           print "Open Url Failed !!! Repeat"
           time.sleep(1)
           repeat_time = repeat_time+1
           if repeat_time == 5:
                return
   #上面整段判斷url能不能開啟


   print "Readint the web ..."
   soup = BeautifulSoup(web.read())
   print "..."
#提取標籤 a
   for tag in tags:

       #避免參數傳遞異常
       try:
           ret = tag['href']
       except:
           print "Maybe not the attr : href"
           continue
       o = urlparse.urlparse(ret)
       #urlparse.urlparse函式的使用
       urlparse模組主要是把url拆分為6部分,並返回元組。並且可以把拆分後的部分再組成一個url。主要有函式有urljoin、urlsplit、urlunsplit、urlparse等。 

urlparse.urlparse(urlstring[, scheme[, allow_fragments]])

    將urlstring解析成6個部分,它從urlstring中取得URL,並返回元組 (scheme, netloc, path, parameters, query, fragment),但是實際上是基於namedtuple,是tuple的子類。它支援通過名字屬性或者索引訪問的部分URL,每個元件是一串字元,也有可能是空的。元件不能被解析為更小的部分,%後面的也不會被解析,分割符號並不是解析結果的一部分,除非用斜線轉義,注意,返回的這個元組非常有用,例如可以用來確定網路協議(HTTP、FTP等等 )、伺服器地址、檔案路徑,等等。
       """
       #Debug I/O
       for _ret in o:
           if _ret == "":
                pass
           else:
                print _ret
       """



       #處理相對路徑url
       if o[0] is "" and o[1] is "":
           print "Fix  Page: " +ret
           url_obj = urlparse.urlparse(web.geturl())
           #獲取web頁面的url,用urlparse函式抽離
           ret = url_obj[0] + "://" + url_obj[1] + url_obj[2] + ret
           #組成一個絕對url
           #保持url的乾淨
           ret = ret[:8] + ret[8:].replace('//','/')
           o = urlparse.urlparse(ret)

                     #這裡不是太完善,但是可以應付一般情況

           if '../' in o[2]:
                paths = o[2].split('/')
               for i inrange(len(paths)):
                    if paths[i] == '..':
                        paths[i] = ''
                        if paths[i-1]:
                            paths[i-1] = ''
                tmp_path = ''
                for path in paths:
                    if path == '':
                        continue
                    tmp_path = tmp_path + '/' +path
                ret =ret.replace(o[2],ret_path)
           print "FixedPage: " + ret


      上面整段都是判斷獲到的url是絕對url還是相對url。如果是相對url,則還需進行重組成完善的絕對url。包括url中含../的情況進行處理。但是處理../的情況不理解!!???

       #協議處理
       if 'http' not in o[0]:
           print "Bad  Page:" + ret.encode('ascii')
           continue

       #url合理性檢驗
       if o[0] is "" and o[1] is not "":
           print "Bad  Page: " +ret
           continue

       #域名檢驗
       if domain not in o[1]:
       #變數domain用來檢驗獲取的所有url是否是改域名下的
           print "Bad  Page: " +ret
           continue

       #整理,輸出
       newpage = ret
       if newpage not in sites:
           print "Add New Page: " + newpage
           pages.add(newpage)
   return pages

#dfs演算法遍歷全站
def dfs(pages):
    #無法獲取新的url說明便利完成,即可結束dfs
   if pages is set():
       return
   global url
   global domain
   global sites
   global visited
   sites = set.union(sites,pages)
   for page in pages:
       if page not in visited:
           print "Visiting",page
           visited.add(page)
           url = page
           pages = get_local_pages(url, domain)
           dfs(pages)

   print "sucess"
 #整段程式下來,一直不知道 變數domain用來檢驗獲取的所有url是否是改域名下的

pages = get_local_pages(url, domain)
dfs(pages)
for i in sites:
print i

整個的大概思路是:
傳入url
判斷能否開啟
開啟url,獲取整個改url的web資訊
提取屬性‘href’
遍歷提取到的連結,這裡叫做url2
判斷url是否為相對url
是的話
對其進行抽離[urlparse.urlparse()]
重組
(處理相對url)
最後檢驗協議、url域名。
帶入遍歷演算法。

最後是web元素的處理
兩個模組
bs4模組和docx模組的使用
**bs4前面有寫
我們重點要講findAll方法的兩個引數:name和attr**
Name: 指的是標籤名,傳入一個標籤名的名稱就可以返回所有固定名稱的標籤名

Attr: 是一個字典儲存需要查詢的標籤引數,返回對應的標籤

Tag.children 表示獲取tag標籤的所有子標籤

Tag.string 表示獲取tag標籤內的所有字串,不用一層一層索引下去尋找字串

Tag.attrs[key] 表示獲取tag標籤內參數的鍵值對鍵為key的值

Tag.img 表示獲取tag標籤的標籤名為img的自標籤(一個)

docx模組:
在使用這個模組的時候,要記清楚如果:

pip install python-docx

easy_install python-docx

兩種方式安裝都不能正常使用的話,就需要下載tar包自己手動安裝

Docx模組是一個可以直接操作生成docx文件的python模組,使用方法極盡簡單:

Demo = Document() #在記憶體中建立一個doc文件

Demo.add_paragraph(data) #在doc文件中新增一個段落

Demo.add_picture(“pic.png”) #doc文件中新增一個圖片

Demo.save(‘demo.docx’) #儲存docx文件

觀察html結構:

b4.png這裡寫圖片描述

我們大致觀察一下結構,定位到文章的具體內容需要找到標籤,然後再遍歷標籤的子標籤即可遍歷到所有的段落,配圖資料

b5.png這裡寫圖片描述

這樣定位到圖片,那麼我們怎麼樣來尋找

from docx import Document
from bs4 import BeautifulSoup
import urllib

url ="http://freebuf.com/news/94263.html"
data = urllib.urlopen(url)

document = Document()

soup = BeautifulSoup(data)
article = soup.find(name ="div",attrs={'id':'contenttxt'}).children
#這段是提取我們所要的資訊
for e in article:
   try:
       if e.img:
           pic_name = ''
           print e.img.attrs['src']
           if 'gif' in e.img.attrs['src']:
                pic_name = 'temp.gif'
           elif 'png' in e.img.attrs['src']:
                pic_name = 'temp.png'
           elif 'jpg' in e.img.attrs['src']:
                pic_name = 'temp.jpg'
           else:
                pic_name = 'temp.jpeg'
           urllib.urlretrieve(e.img.attrs['src'], filename=pic_name)

           #下面我們再來看看 urllib 模組提供的 urlretrieve() 函式。urlretrieve() 方法直接將遠端資料下載到本地。

#1
>>> help(urllib.urlretrieve)
#2
Help on function urlretrieve in module urllib:
#3

#4
urlretrieve(url, filename=None, reporthook=None, data=None)
#引數 finename 指定了儲存本地路徑(如果引數未指定,urllib會生成一個臨時檔案儲存資料。)
#引數 reporthook 是一個回撥函式,當連線上伺服器、以及相應的資料塊傳輸完畢時會觸發該回調,我們可以利用這個回撥函式來顯示當前的下載進度。
#引數 data 指 post 到伺服器的資料,該方法返回一個包含兩個元素的(filename, headers)元組,filename 表示儲存到本地的路徑,header 表示伺服器的響應頭。


           document.add_picture(pic_name)
   except:
       pass
   if e.string:
       print e.string.encode('gbk','ignore')
       document.add_paragraph(e.string)

document.save("freebuf_article.docx")
print "success create a document"