一、簡單爬蟲框架
簡單爬蟲框架由四個部分組成:URL管理器、網頁下載器、網頁解析器、調度器,還有應用這一部分,應用主要是NLP配合相關業務。
它的基本邏輯是這樣的:給定一個要訪問的URL,獲取這個html及內容(也可以獲取head和cookie等其它信息),獲取html中的某一類鏈接,如a標簽的href屬性。從這些鏈接中繼續訪問相應的html頁面,然后獲取這些html的固定標簽的內容,並把這些內容保存下來。
一些前提:;所有要爬取的頁面,它們的標簽格式都是相同的,可以寫一個網頁解析器去獲取相應的內容;給定的URL(要訪問的資源)所獲得的html,它包含的標簽鏈接是可以篩選的,篩選后的標簽鏈接(新的URL)會被繼續請求其html文檔。調度器是一個循環體,循環處理這些URL、請求以及html、網頁解析。
1.運行流程
調度器是一個主循環體,負責不斷重復執行URL管理器、下載器、解析器。URL是管理新的URL的添加、舊的URL的去除,以及URL的去重和記錄。下載器顧名思義,就是根據URL,發送http請求,獲取utf-8編碼的字節流的html文件數據。解析器負責將html還原成DOM對象,並提供一套類似js的DOM操作的方法,從html中獲取節點、屬性、文本、甚至是樣式等內容。
2.URL管理器
URL管理器有兩個功能,獲取待添加的URL--判斷它是否在已被讀取的URL集合里--[No]判斷它是否在待讀取的URL集合里--[No]添加到待讀取的URL集合里。否則就直接拋棄。
URL管理器一般放在內存、關系型數據庫和緩存數據庫里。python里可以使用set()集合去重。
3.網頁下載器
向給定的URL發送請求,獲取html。python的兩個模塊。內置urllib模塊和第三方模塊request。python3將urllib2封裝成了urllib.request模塊。
1 # 網頁下載器代碼示例 2 import urllib 3 4 url = "http://www.baidu.com" 5 6 print("第一種方法: 直接訪問url") 7 response1 = urllib.request.urlopen(url) 8 print(response1.getcode()) # 狀態碼 9 print(len(response1.read())) # read讀取utf-8編碼的字節流數據 10 11 print("第二種方法: 設置請求頭,訪問Url") 12 request = urllib.request.Request(url) # 請求地址 13 request.add_header("user-agent", "mozilla/5.0") # 修改請求頭 14 response2 = urllib.request.urlopen(request) 15 print(response2.getcode()) 16 print(len(response2.read())) 17 18 import http.cookiejar # 不知道這是啥 19 20 print("第三種方法: 設置coockie,返回的cookie") 21 # 第三種方法的目的是為了獲取瀏覽器的cookie內容 22 cj = http.cookiejar.CookieJar() 23 opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj)) 24 urllib.request.install_opener(opener) 25 response3 = urllib.request.urlopen(url) 26 print(response3.getcode()) 27 print(len(response3.read())) 28 print(cj) # 查看cookie的內容
4.網頁解析器
將utf-8編碼的字節碼重新重新解析為html。因為數據傳輸是字節數據,所以網頁下載器下載的內容需要重新解析。
提供DOM對象[html文檔解構]的操作方法。和js類似。包括節點、標簽元素、屬性[包括name、class、style、value等等]、樣式、內容等的操作。從而能夠獲取特定的內容。
python的BeautifulSoup模塊(bs4)。以下代碼可直接在bs4模塊官方文檔中獲取和運行。
1 from bs4 import BeautifulSoup 2 from re import compile 3 html_doc = """ 4 <html><head><title>The Dormouse's story</title></head> 5 <body> 6 <p class="title"><b>The Dormouse's story</b></p> 7 8 <p class="story">Once upon a time there were three little sisters; and their names were 9 <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, 10 <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and 11 <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; 12 and they lived at the bottom of a well.</p> 13 14 <p class="story">...</p> 15 """ 16 17 soup = BeautifulSoup(html_doc, "html.parser") 18 print(soup.prettify()) 19 print(soup.title) 20 print(soup.title.name) 21 print(soup.title.string) 22 print(soup.title.parent.name) 23 print(soup.p) 24 print(soup.p['class']) 25 print(soup.a) 26 print(soup.find_all(href=compile(r"/example.com/\S*"))) 27 print(soup.find_all('a')) 28 print(soup.find(id="link3")) 29 print(soup.get_text()) 30 print(soup.find("p", attrs={"class": "story"}).get_text()) 31 32 for link in soup.find_all('a'): 33 print(link.get('href'))
二、簡單示例
爬取百度百科上詞條為python的以href='/tem/'開頭的所有相關網頁的詞條簡介。
1 from re import compile 2 from html.parser import HTMLParser 3 from bs4 import 4 5 # url管理器 6 class UrlManager(object): 7 """ 8 url管理器主要有三個功能:add_new_url添加新的待爬取的頁面;get_new_url刪除已爬取的頁面;標記待爬取的和已爬取的頁面。 9 """ 10 def __init__(self): 11 self.new_urls = set() 12 self.old_urls = set() 13 def add_new_url(self, url): 14 if url is None: 15 return 16 # 如果傳入的url既不在待爬取的url里又不在爬過的url里,說明它是待爬取的url 17 if url not in self.new_urls and url not in self.old_urls: 18 self.new_urls.add(url) 19 20 def add_new_urls(self, urls): 21 if urls is None or len(urls) == 0: 22 return 23 for url in urls: 24 self.add_new_url(url) 25 26 def has_new_url(self): 27 return len(self.new_urls) != 0 28 29 def get_new_url(self): 30 new_url = self.new_urls.pop() # 從待爬去的url中剔除要爬取的目標 31 self.old_urls.add(new_url) # 添加到 32 return new_url 33 34 # 簡單的下載器 35 class HtmlDownloader(object): 36 def download(self, url): 37 if url is None: 38 return None 39 response = urllib.request.urlopen(url) 40 if response.getcode() != 200: 41 return None 42 return response.read() 43 44 # 解析器 45 class HtmlParser(object): 46 def _get_new_urls(self, page_url, soup): 47 # 這里要提一下,百度百科python詞匯的url是https://baike.baidu.com/item/Python/407313 48 # 頁面中的a標簽的href屬性都類似href="/item/%E6%95%99%E5%AD%A6"這種屬性 49 # 在處理時,需要加上baike.baidu.com保證url資源定位符的完整性。后面只需匹配"/item/" 50 new_urls = set() 51 links = soup.find_all('a', href=compile(r"/item/\S*")) 52 for link in links: 53 new_url = link["href"] 54 new_full_url = urllib.parse.urljoin(page_url, new_url) 55 new_urls.add(new_full_url) 56 return new_urls 57 58 def _get_new_data(self, page_url, soup): 59 res_data = {} 60 res_data["url"] = page_url 61 # 爬取標題 62 # <dd class="lemmaWgt-lemmaTitle-title"></dd><h1>Python</h1> 63 title_node = soup.find("dd", attrs={"class": "lemmaWgt-lemmaTitle-title"}).find("h1") 64 res_data["title"] = title_node.get_text() 65 # 爬取簡介內容 66 # <div class="lemma-summary" label-module="lemmaSummary"></div> 67 # 這個div下的所有div里的text 68 summary_node = soup.find('div', attrs={"class": "lemma-summary", "label-module":"lemmaSummary"}) 69 res_data["summary"] = summary_node.get_text() 70 return res_data 71 72 def parse(self, page_url, html_doc): 73 if page_url is None or html_doc is None: 74 return 75 # 解析成了一個整個的DOM對象,也就是純html格式的文件 76 soup = BeautifulSoup(html_doc, "html.parser", from_encoding="utf-8") 77 new_urls = self._get_new_urls(page_url, soup) 78 new_data = self._get_new_data(page_url, soup) 79 # print("page_url: %r, new_urls: %r, new_data: %r" % (page_url, new_urls, new_data)) 80 return new_urls, new_data 81 82 # 輸出器 83 class HtmlOutputer(object): 84 def __init__(self): 85 self.datas = [] 86 def collect_data(self, data): 87 if data is None: 88 return 89 self.datas.append(data) 90 def output_html(self): 91 fout = open("output.html", 'w', encoding="UTF-8") 92 fout.write("<html>") 93 fout.write("<meta http-equiv='content-type' content='text/html;charset=utf-8'>") 94 fout.write("<body>") 95 fout.write("<table>") 96 for data in self.datas: 97 fout.write("<tr>") 98 fout.write("<td>%s</td>" %data['url']) 99 fout.write("<td>%s</td>" %data['title']) 100 fout.write("<td>%s</td>" %data['summary']) 101 fout.write("</tr>") 102 fout.write("</table>") 103 fout.write("</body>") 104 fout.write("</html>") 105 106 class SpiderMain(object): 107 def __init__(self): 108 self.urls = UrlManager() 109 self.downloader = HtmlDownloader() 110 self.parser = HtmlParser() 111 self.outputer = HtmlOutputer() 112 113 def craw(self, root_url): 114 count = 1 115 self.urls.add_new_url(root_url) 116 while self.urls.has_new_url(): 117 try: 118 new_url = self.urls.get_new_url() 119 html_cont = self.downloader.download(new_url) 120 # print("\033[1;36m %r \033[0m" % html_cont.decode("utf-8")) 121 new_urls, new_data = self.parser.parse(new_url, html_cont) 122 self.urls.add_new_urls(new_urls) 123 self.outputer.collect_data(new_data) 124 if count == 11:break 125 print("\033[1;36m [CRAW]\033[0m : %d %r" %(count, new_url)) 126 count += 1 127 except Exception as e: 128 print("craw failed") 129 print(e) 130 self.outputer.output_html()
運行結果如下:
打開保存的out.html,內容如下: