Python-定向爬蟲的簡單使用

  • 前言

初次接觸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í)行上述操作。

一般來說,爬蟲程序的簡單組成基本上可分為:URL管理器、數(shù)據(jù)下載器、數(shù)據(jù)解析器、應(yīng)用程序等幾部分。

  1. URL管理器:管理URL,包括已經(jīng)爬取過數(shù)據(jù)的URL和未爬取即將爬取的URL。
  2. 數(shù)據(jù)下載器:通過一個URL下載網(wǎng)頁,將網(wǎng)頁轉(zhuǎn)換成一個字符串,網(wǎng)頁下載器有urllib2(Python官方基礎(chǔ)模塊)包括需要登錄、代理、和cookie,requests(第三方包)。
  3. 數(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)行解析的。
  4. 應(yīng)用程序:將數(shù)據(jù)解析器中提取的數(shù)據(jù)進(jìn)行存儲或輸出。


    援引網(wǎng)絡(luò)上一張爬蟲工作流程的圖片.png
  • 簡單實踐

大致了解了下爬蟲的組成,我們就可以進(jìn)行實踐了,這里我們用的是Python語言,項目也很簡單,只有幾個文件,具體目錄如下


目錄.png
  1. 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
      
  2. 數(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')
      
  3. 數(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
      
  4. 輸出/應(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()
      
  5. 啟動程序入口(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()
      
  • Q&A

    Q1: https網(wǎng)絡(luò)請求可能會驗證不通過
    A1:在html_download.pyimport ssl,并加入如下代碼

    ssl._create_default_https_context = ssl._create_unverified_context
    

    Q2:urllib2無法導(dǎo)入
    A2:可以使用from urllib import request,貌似3.x版本已經(jīng)廢棄了urllib2

    Q3:urlpath無法導(dǎo)入
    A3:可以使用from urllib.parse import urljoin

    Q4: 獲取不到數(shù)據(jù)
    A4: 可能是百度百科換了URL格式,或者換了網(wǎng)頁中元素標(biāo)簽,可查看網(wǎng)頁源碼,進(jìn)行微調(diào)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容