1. 程式人生 > >python爬蟲實戰(一)

python爬蟲實戰(一)

看了網上好多人寫的爬蟲,架構風格都不是很喜歡,前幾天在GitHub上翻到一個專案,主要是結構特別好,那種面向物件的風格很受我的喜歡,今天按照這種方式寫了兩個爬蟲分享給大家
廢話不多說,直接上程式碼
一.利用requests,BeautifulSoup庫爬取CSDN上的1000篇部落格
一共四個檔案:
1.spider_mian:排程器

import re

from CSDN_spider import html_parser, save_txt, html_downloader

#爬蟲總排程器
class SpiderMain(object):
    #構造方法初始化下載器,解析器,儲存器
def __init__(self): self.downloader = html_downloader.Downloader() self.html_parser = html_parser.Parser() self.saver = save_txt.Saver() #爬蟲方法,構造引數:開始網址,要爬取的文章數量,和頁面號 def crawl(self,root_link,article_count,page_num): count = 1 link = root_link #迴圈迭代,直到爬到一定數量的文章
while 1: #通過下載器,解析器得到網頁上所有文章連結 page = self.downloader.download(link) urls = self.html_parser.parse_link(page) #對每個文章的連線進行爬蟲 for url in urls: print('crawl %d:%s' % (count ,url)) article_html = self.downloader.download(url) article_done = self.html_parser.parse_article(article_html) #判斷文章為空,則不進行儲存
if article_done is not None: self.saver.save_article(article_done,count,url) count += 1 if count==article_count: break if count == article_count: break #進行下一次的爬取 page_num += 1 new_link = re.sub(r'p=\d+','p=%d'%page_num,link) link = new_link if __name__ == '__main__': # root_link = 'http://so.csdn.net/so/search/s.do?p=1&q=Lucene&t=blog&domain=&o=&s=&u=&l=&f=&rbg=0' # root_link = ' http://so.csdn.net/so/search/s.do?p=2&q=%E5%A4%A7%E6%95%B0%E6%8D%AE&t=blog&domain=&o=&s=&u=&l=&f=&rbg=0' root_link = 'http://so.csdn.net/so/search/s.do?p=1&q=%E4%BA%91%E8%AE%A1%E7%AE%97&t=blog&o=&s=&l=' article_count = 1000 page_num = 1 spider = SpiderMain() spider.crawl(root_link,article_count,page_num)

將爬蟲的總排程程度單獨寫在一個類中,這樣可維護性,可讀性都比較好
2.html_downloader:下載器

import requests


class Downloader(object):
    #下載方法
    def download(self, url):
        if url is None or len(url)==0:
            return None
        try:
            #注意要改變頭資訊
            r = requests.get(url,headers={'user-Agent':'Mozilla/5.0'})
            r.raise_for_status()
            r.encoding=r.apparent_encoding
            return r.text
        except:
            print('download error')

3.html_parser:解析器

import re
from bs4 import BeautifulSoup


class Parser(object):
    #解析網頁的方法
    def parse_link(self, page):
        #這裡養成習慣做判斷
        if page is None:
            return None
        soup = BeautifulSoup(page,'html.parser')
        #用beautifulsoup+正則獲取連結
        links = soup.find_all('a',href=re.compile(r'.*/article/details/\d*'))
        #用set防止重複連結
        urls = set()
        for link in links:
            urls.add(link['href'])
        return urls
    #解析文章的方法
    def parse_article(self, article_html):
        if article_html is None:
            return None
        try:
            #分別解析文章的標題和內容,以字典的形式儲存
            soup = BeautifulSoup(article_html, 'html.parser')
            article_done = {}

            title = soup.find('h1',class_='csdn_top').get_text()

            article_done['title'] = title
            content = soup.find('div',id='article_content').get_text()
            article_done['content'] = content
            return article_done
        except:
            return None

4.Saver:儲存器

class Saver(object):
    def save_article(self, article_done,num,url):
        #注意改變編碼
        with open('./article/'+str(num)+'.txt','w',encoding='utf-8') as f:
            f.writelines('title:'+article_done['title']+'\n')
            f.writelines('url:'+url+'\n')
            f.writelines('content:'+article_done['content'])

我都在程式碼中寫了詳細的註釋,應該能看懂

5.踩過的坑兒
這個爬蟲很簡單,寫的話用不了多長時間,但是還是有一些細節問題讓我除錯程式也花費了好多時間
1.在每個解析或下載的方法中,一定要判斷傳進來的引數是否為空,否則很可能發生異常,引發爬蟲中斷。
2.容易引發異常的地方用try except 處理,防止爬蟲中斷
3.裝入url一定要用set!因為一個頁面中可能有不同的url,這個我真的查了好久才發現
4.爬蟲的時候要有良好的使用者提示,輸出進度,一直等著很蛋疼!
5.注意修改頭資訊,有的網站反爬蟲技術,不讓爬,這個要養成習慣
6.注意修改爬蟲response物件編碼,具體參照我上面的程式碼
7.要修改輸出檔案的編碼,utf-8
8.養成良好的程式碼風格,將不同功能的模組分離開,就像上面我寫的這種,一個類負責一個功能模組

二.爬取百度百科的1000個詞條資訊
1.spider_main

class SpiderMain(object):
    #初始化資訊,這裡加一個url管理器
    def __init__(self):
        self.urls = url_manager.UrlManager()
        self.downloader = html_downloader.Downloader()
        self.parser = html_parser.Parser()
        self.outputer = html_outputer.Outputer()
    def crawl(self,root_url):
        #將根路徑新增到url管理器中
        self.urls.add_url(root_url)
        count = 1
        #迭代爬取,直到滿足條件
        while self.urls.has_new_url():
                #從url管理器得到新的url
                new_url = self.urls.get_new_url()
                #輸出進度
                print('crawl %d:%s'%(count,new_url))
                #下載器下載內容
                html_cont = self.downloader.download(new_url)
                #解析器解析內容
                new_urls ,new_data = self.parser.parse(new_url,html_cont)
                #將解析的url新增到url管理器中
                self.urls.add_urls(new_urls)
                #將解析後的內容加入處理器
                self.outputer.collect(new_data)
                if(count==1000):
                    break
                count += 1

        self.outputer.output()


if __name__ == '__main__':
    root_url = 'https://baike.baidu.com/item/蜘蛛/6152';
    obj_spider = SpiderMain()
    obj_spider.crawl(root_url)

2.html_downloader
這部分程式碼跟上面沒有太大的變化,就不寫註釋了


class Downloader(object):
    def download(self, new_url):
        if new_url is None or len(new_url)==0:
            return None
        try:
            r = requests.get(new_url,headers={'user-Agent':'Mozilla/5.0'})
            r.raise_for_status()
            r.encoding = r.apparent_encoding
            return r.text
        except:
            print('download error')

3.url_manager
這裡新加了個url管理器,這個類負責對所有的類進行管理,這樣就可以迭代進行爬取,我覺得有點像廣度優先搜尋的意思,只不過這裡我們用一個集合來維護

class UrlManager(object):
    def __init__(self):
        #注意這裡要用集合,防止有同樣的url
        self.new_urls = set()
        self.old_urls = set()
    #取出url
    def get_new_url(self):
        url = self.new_urls.pop()
        self.old_urls.add(url)
        return url
    #判斷是否有新的url
    def has_new_url(self):
        return len(self.new_urls) != 0
    #新增新的url
    def add_urls(self, new_urls):
        if new_urls is None or len(new_urls)==0:
            return
        for url in new_urls:
            self.new_urls.add(url)
    #新增原始url
    def add_url(self, root_url):
        if root_url is None:
            return
        if root_url not in self.new_urls and root_url not in self.old_urls:
            self.new_urls.add(root_url)

4.html_parser

import re

from urllib.parse import urljoin
from bs4 import BeautifulSoup


class Parser(object):
    #解析url,解析出新的url和要爬取的內容,這裡為了解耦也將這兩個方法分開寫
    def parse(self, new_url, html_cont):
        if new_url is None or html_cont is None:
            return
        soup = BeautifulSoup(html_cont,'html.parser')
        new_urls = self.get_urls(soup,new_url)
        new_data = self.get_data(soup, new_url)
        return new_urls,new_data

    def get_urls(self, soup, url):
        new_urls = set()

        links = soup.find_all('a',href=re.compile(r'/item/(.*)'))
        #對爬取的連結迴圈加入
        for link in links:
            new_url = link['href']
            #這個方法很好用,自動拼接,推薦大家使用
            full_url = urljoin(url,new_url)
            new_urls.add(full_url)
        return new_urls

   # < dd class ="lemmaWgt-lemmaTitle-title" >
    #<div class="lemma-summary" label-module="lemmaSummary">
    def get_data(self, soup, new_url):
        #用字典儲存
        data = {}
        title_node = soup.find('dd',class_="lemmaWgt-lemmaTitle-title").find('h1')
        #bs提供了大量的方法來獲取你想要的內容
        data['title'] = title_node.get_text()
        summary_node = soup.find('div',class_='lemma-summary')
        data['summary'] = summary_node.get_text()
        return data

5.outputer
這部分可以根據需要隨便寫,我這裡是輸出到本地檔案中

class Outputer(object):
    def collect(self, new_data):

        with open('baidu.txt','a',encoding='utf-8') as f:
            f.writelines(new_data['title']+':'+'\n')
            f.writelines(new_data['summary']+'\n')

人生苦短,我用python,python寫爬蟲真的很方便