-
前言
初次接觸Python,是以為測試同事用來做自動化測試,這兩天有空“研究”了一下Python網(wǎng)絡(luò)爬蟲,所謂“研究”,其實就是了解,并跟著慕課網(wǎng)上的教學(xué)視頻,寫了一個爬取百度百科的定向爬蟲。Demo傳送門
注意:小生是剛接觸Python,這里只是粗略記錄下我的學(xué)習(xí),所以深度優(yōu)先,文中如有錯誤的地方請在留言處批評指正,小生感激不盡。
-
爬蟲-百度百科
簡單說,爬蟲可以理解為網(wǎng)頁蜘蛛,指通過我們特定的規(guī)則,自動化的從互聯(lián)網(wǎng)獲取我們需要的數(shù)據(jù)的一種技術(shù)手段。百度百科中有些簡單的介紹,感興趣的同學(xué)可以預(yù)先瀏覽學(xué)習(xí)下。
-
原理
簡單的網(wǎng)絡(luò)爬蟲,就是從一個網(wǎng)頁的URL開始,下載該URL對應(yīng)的網(wǎng)頁,通過正則或其他技術(shù)手段從下載的內(nèi)容中獲取想要的數(shù)據(jù)進(jìn)行存儲或輸出,并提取該URL中包含的其他URL,放入URL管理隊列中,重復(fù)執(zhí)行上述操作。
-
爬蟲組成-結(jié)構(gòu)
一般來說,爬蟲程序的簡單組成基本上可分為:URL管理器、數(shù)據(jù)下載器、數(shù)據(jù)解析器、應(yīng)用程序等幾部分。
- URL管理器:管理URL,包括已經(jīng)爬取過數(shù)據(jù)的URL和未爬取即將爬取的URL。
- 數(shù)據(jù)下載器:通過一個URL下載網(wǎng)頁,將網(wǎng)頁轉(zhuǎn)換成一個字符串,網(wǎng)頁下載器有urllib2(Python官方基礎(chǔ)模塊)包括需要登錄、代理、和cookie,requests(第三方包)。
- 數(shù)據(jù)解析器:將數(shù)據(jù)下載器下來的數(shù)據(jù),按照我們的規(guī)則,進(jìn)行數(shù)據(jù)提取。也可以根據(jù)DOM樹的解析方式來解析。網(wǎng)頁解析器有正則表達(dá)式(直觀,將網(wǎng)頁轉(zhuǎn)成字符串通過模糊匹配的方式來提取有價值的信息,當(dāng)文檔比較復(fù)雜的時候,該方法提取數(shù)據(jù)的時候就會非常的困難)、html.parser(Python自帶的)、beautifulsoup(第三方插件,可以使用Python自帶的html.parser進(jìn)行解析,也可以使用lxml進(jìn)行解析,相對于其他幾種來說要強(qiáng)大一些)、lxml(第三方插件,可以解析 xml 和 HTML),html.parser 和 beautifulsoup 以及 lxml 都是以 DOM 樹的方式進(jìn)行解析的。
-
應(yīng)用程序:將數(shù)據(jù)解析器中提取的數(shù)據(jù)進(jìn)行存儲或輸出。
援引網(wǎng)絡(luò)上一張爬蟲工作流程的圖片.png
-
簡單實踐
大致了解了下爬蟲的組成,我們就可以進(jìn)行實踐了,這里我們用的是Python語言,項目也很簡單,只有幾個文件,具體目錄如下

-
URL管理器(url_manager.py):
-
class UrlManager(object):
- init(self):初始化操作
def __init__(self): # 使用set存儲URL,防止重復(fù) self.new_urls = set() self.old_urls = set()- add_new_url(self, url):添加單個URL
# 添加單個URL def add_new_url(self, url): # 檢查是否為空 if url is None: return # 檢查是否已經(jīng)添加到待爬取和未爬取集合中,若在return if url in self.new_urls and url in self.old_urls: return else: self.new_urls.add(url)- add_new_urls(self, urls):添加多個url
# 添加多個url def add_new_urls(self, urls): if urls is None: return for url in urls: self.add_new_url(url)- has_new_url(self):是否還有未爬取數(shù)據(jù)的URL
# 是否還有未爬取數(shù)據(jù)的URL def has_new_url(self): return len(self.new_urls) != 0- get_new_url(self):獲取一個待爬取數(shù)據(jù)的URL
# 獲取一個待爬取數(shù)據(jù)的URL def get_new_url(self): # 在未爬去數(shù)據(jù)的URL中獲取將要爬去數(shù)據(jù)的URL,并在new_urls中刪除 new_url = self.new_urls.pop() # 將new_url添加到已經(jīng)使用的URL中 self.old_urls.add(new_url) # 返回將要爬去數(shù)據(jù)的URL return new_url
-
-
數(shù)據(jù)下載器(html_download.py):
-
class HtmlDownloader(object):
- 設(shè)置https自動校驗
# 如果訪問的是https,會自動校驗,此句停止自動校驗 ssl._create_default_https_context = ssl._create_unverified_context- download(self):下載網(wǎng)頁
@staticmethod def download(url): # 下載URL中的數(shù)據(jù) if url is None: return with request.urlopen(url) as f: if f.getcode() != 200: print('請求失敗') return None return f.read().decode('utf-8')
-
-
數(shù)據(jù)解析器(html_parser.py):
-
class HtmlParser(object):
- _get_new_urls(soup, page_url):從結(jié)果中提取符合規(guī)則的待爬取URL
@staticmethod def _get_new_urls(soup, page_url): # /item/Python/407313,網(wǎng)頁URL相對路徑 # new_urls 用于存放提取出來的新URL的集合,集合內(nèi)不會存在相同元素 new_urls = set() # links 是通過BeatifulSoup find_all按照一定的規(guī)則獲取到的所有l(wèi)ink,find_all方法查詢所有符合條件的 links = soup.find_all('a', href=re.compile(r'/item/\w+/\d+')) for link in links: new_url = link['href'] # 拼接全路徑 new_full_url = urljoin(page_url, new_url) # 向集合中添加拼接后的全路徑 new_urls.add(new_full_url) print('page_url: %s, link: %s, new_url: %s, full_url: %s' % (page_url, link, new_url, new_full_url)) return new_urls- _get_new_data(soup, page_url):從結(jié)果中提取符合規(guī)則的數(shù)據(jù)
@staticmethod def _get_new_data(soup, page_url): res_data = {'url': page_url} # <dd class="lemmaWgt-lemmaTitle-title"><h1>Python</h1> # title_node = <h1>Python</h1> # 獲取標(biāo)題節(jié)點(diǎn) title_node = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1') res_data['title'] = title_node.get_text() # <div class="lemma-summary" label-module="lemmaSummary"> # summary 獲取簡介節(jié)點(diǎn) summary_node = soup.find('div', class_='lemma-summary') res_data['summary'] = summary_node.get_text() return res_data- parse(self, page_url, html_cont)
def parse(self, page_url, html_cont): if page_url is None or html_cont is None: return soup = BeautifulSoup(html_cont, 'html.parser') # 提取網(wǎng)頁中的其他URL new_urls = self._get_new_urls(soup, page_url) new_data = self._get_new_data(soup, page_url) return new_urls, new_data
-
-
輸出/應(yīng)用程序(html_output.py):
-
class HtmlOutputer(object):
- init(self):
def __init__(self): #初始化數(shù)組 self.data = []- collect_data(self, data):將每一條解析出來的結(jié)果,存放到數(shù)組中,以便在HTML頁面中輸出
# 將每一條解析出來的結(jié)果,存放到數(shù)組中,以便在HTML頁面中輸出 def collect_data(self, data): self.data.append(data)- output_html(self):將結(jié)果遍歷輸出到網(wǎng)頁resource.html中
def output_html(self): with open('resource.html', 'w') as file: file.write('<html>') file.write('<head>') file.write('<meta charset = "utf-8">') file.write('<title>') file.write('爬取結(jié)果') file.write('</title>') file.write('</head>') file.write('<body>') file.write('<table border = "1" cellspacing = "0">') file.write('<tr><td>標(biāo)題</td><td>鏈接</td><td>描述簡介</td></tr>') for data in self.data: print(data) string = "<tr><td>%s</td><td><a href=%s>%s</a></td><td>%s</td></tr>" % (data['title'], data['url'], data['url'], data['summary']) file.write(string) file.write('</table>') file.write('</body>') file.write('</html>') file.close()
-
-
啟動程序入口(spider_main.py)
- class SpiderMain(object):
- init(self):
def __init__(self): self.urls = url_mananger.UrlManager() self.downloader = html_download.HtmlDownloader() self.parser = html_parser.HtmlParser() self.outputer = html_output.HtmlOutputer()- craw(self, root_url):
def craw(self, root_url): if root_url is None: return count = 1 # 向URL管理器中添加root_url self.urls.add_new_url(root_url) while self.urls.has_new_url(): try: # 獲取要爬去數(shù)據(jù)的URL new_url = self.urls.get_new_url() # 下載URL對應(yīng)的html html_cont = self.downloader.download(new_url) # 解析下載的數(shù)據(jù) new_urls, new_data = self.parser.parse(new_url, html_cont) # 將解析出來的相關(guān)urls添加到urls.new_urls中 self.urls.add_new_urls(new_urls) # 將解析出來的數(shù)據(jù)保存到outputer中 self.outputer.collect_data(new_data) # 只爬取100條數(shù)據(jù) if count == 100: break count = count + 1 except ValueError: print('aaa,failed') # 輸出一個html頁面 self.outputer.output_html()- main(): 啟動初始化
def main(): # 設(shè)置入口URL root_url = 'https://baike.baidu.com/item/Python/407313' # 實例化一個爬蟲對象 obj_spider = SpiderMain() obj_spider.craw(root_url)- 啟動程序
if __name__ == '__main__': # 啟動 main()
- class SpiderMain(object):
-
Q&A
Q1: https網(wǎng)絡(luò)請求可能會驗證不通過
A1:在html_download.py中import ssl,并加入如下代碼ssl._create_default_https_context = ssl._create_unverified_contextQ2:
urllib2無法導(dǎo)入
A2:可以使用from urllib import request,貌似3.x版本已經(jīng)廢棄了urllib2Q3:
urlpath無法導(dǎo)入
A3:可以使用from urllib.parse import urljoinQ4: 獲取不到數(shù)據(jù)
A4: 可能是百度百科換了URL格式,或者換了網(wǎng)頁中元素標(biāo)簽,可查看網(wǎng)頁源碼,進(jìn)行微調(diào)。
