使用python下載新浪博客

7個月前的一篇todo-list:一個下載新浪博客工具的to-do list
今天終于可以說是完工了际看。

代碼鏈接

主要的技術(shù)點:

  1. 使用urllib和urllib2獲取網(wǎng)頁內(nèi)容
  2. 使用BeautifulSoup和re來解析網(wǎng)頁內(nèi)容

編碼思路:

一深滚、獲取博文列表

我想要下載的目標(biāo)博客:纏中說禪的博客
分析此博客赏殃,發(fā)現(xiàn)點擊“博客目錄”后可獲取較調(diào)理的信息:

屏幕快照 2015-09-08 上午12.07.51.png

屏幕快照 2015-09-08 上午12.12.39.png

發(fā)現(xiàn)以下幾點:

  1. 左側(cè)有博文分類谤狡,可通過此處獲取感興趣的分類
  2. 下方有當(dāng)前分類的所有博文所占的頁數(shù),可通過此處獲得總工作量

class Spider:

def __init__(self, indexUrl):
    self.indexUrl = indexUrl
    content = indexUrl.split('/')[-1].split('_')
    self.userID = content[1]
    self.defaultPage = self.getPage(self.indexUrl) 
    
def getPage(self, indexUrl):
    '''獲取indexUrl頁面'''
    request = urllib2.Request(indexUrl)
    response = urllib2.urlopen(request)
    return response.read().decode('utf-8')

def getPageNum(self,page):
    '''計算有幾頁博客目錄'''
    pattern = re.compile('<li class="SG_pgnext">', re.S)
    result = re.search(pattern, page)
    if result:
        print u"目錄有多頁,正在計算……"
        pattern2 = re.compile(u'<li class="SG_pgnext">.*?>共(.*?)頁', re.S)
        num = re.search(pattern2, page)
        pageNum = str(num.group(1))
        print u"共有", pageNum, u"頁"
    else:
        print u"只有1頁目錄"
        pageNum = 1
    return int(pageNum)

博客目錄的URL(http://blog.sina.com.cn/s/articlelist_1215172700_0_1.html)羹铅,
其中1215172700是用戶ID,后面的0表示第一個分類“全部博文”愉昆,最后的1表示是次分類的第1頁。
在Spider類初始化時將此URL解析麻蹋,并傳入getPage函數(shù)中跛溉,獲取網(wǎng)頁HTML。

用正則表達式(re)來解析HTML其實并不是個好方法扮授,原因見這里:You can't parse [X]HTML with regex. Because HTML can't be parsed by regex. Regex is not a tool that can be used to correctly parse HTML.

在之后解析每篇博客內(nèi)容時芳室,re就無能為力了,我只好去使用BeautifulSoup刹勃,但是在前期我卻是參考別的文章使用了正則表達堪侯。

在getPageNum函數(shù)中使用re來獲取了當(dāng)前分類的總頁數(shù)。

然而我們剛開始的時候并不知道要選擇哪個分類荔仁,所以要將這些信息顯示出來供用戶選擇伍宦。

def getTypeNum(self):
    '''計算有幾種分類'''
    pattern = re.compile('<span class="SG_dot">.*?<a href="(.*?)".*?>(.*?)</a>.*?<em>(.*?)</em>', re.S)
    result = re.findall(pattern, self.defaultPage)
    pattern2 = re.compile(u'<strong>全部博文</strong>.*?<em>(.*?)</em>', re.S)
    result2 = re.search(pattern2, self.defaultPage)
    self.allType = {}
    i = 0
    self.allType[i] = (self.indexUrl, u"全部博文", result2.group(1)[1:-1])
    for item in result:
        i += 1
        self.allType[i] = (item[0], item[1], item[2][1:-1])
    print u"本博客共有以下", len(self.allType), "種分類:"
    for i in range(len(self.allType)):
        print "ID: %-2d  Type: %-30s Qty: %s" % (i, self.allType[i][1], self.allType[i][2])

依然是使用re。在該函數(shù)中獲取各分類對應(yīng)的URL乏梁。

現(xiàn)在的流程梳理下就是這樣的:

  1. 程序獲取所有的博文分類
  2. 用戶選擇感興趣的分類
  3. 程序獲取該分類的URL和頁數(shù)
  4. 程序獲取并解析每篇文章(下一章)

二次洼、解析文章

首先會根據(jù)分類和頁數(shù),得到具體某一頁的博文列表的URL遇骑。具體規(guī)則上面已提到卖毁。然后需要將此頁中的所有博客的URL解析出來。

def getBlogList(self,page):
    '''獲取一頁內(nèi)的博客URL列表'''
    pattern = re.compile('<div class="articleCell SG_j_linedot1">.*?<a title="" target="_blank" href="(.*?)">(.*?)</a>', re.S)
    result = re.findall(pattern, page)
    blogList = []
    for item in result:
        blogList.append((item[0], item[1].replace(' ', ' ')))
    return blogList

依然是使用re落萎。

def mkdir(self,path):
    isExist = os.path.exists(path)
    if isExist:
        print u"名為", path, u"的文件夾已經(jīng)存在"
        return False
    else:
        print u"正在創(chuàng)建名為", path, u"的文件夾"
        os.makedirs(path)

def saveBlogContent(self,path,url):
    '''保存url指向的博客內(nèi)容'''
    page = self.getPage(url)
    blogTool = sinaBlogContentTool(page)
    blogTool.parse()
    
    filename =  path + '/' + blogTool.time + '  ' + blogTool.title.replace('/', u'斜杠') + '.markdown'
    with open(filename, 'w+') as f:
        f.write("URL: "+url)
        f.write("標(biāo)簽:")
        for item in blogTool.tags:
            f.write(item.encode('utf-8'))
            f.write(' ')
        f.write('\n')
        f.write("類別:")
        f.write(blogTool.types.encode('utf-8'))
        f.write('\n')
        picNum = 0
        for item in blogTool.contents:
            if item[0] == 'txt':
                f.write('\n')
                f.write(item[1].encode('utf-8'))
            elif item[0] == 'img':
                f.write('\n')
                f.write('!['+ str(picNum) + '](' + item[1] + ')')
                picNum += 1
    
    print u"下載成功"

接下來就是解析博客亥啦,保存內(nèi)容至本地。其中創(chuàng)建文件名時需要注意“/\”此類符號练链,我的做法是將符號變?yōu)槲淖帧靶备堋薄?/p>

優(yōu)于解析博客內(nèi)容較為復(fù)雜翔脱,我創(chuàng)建一個class專門解析。

屏幕快照 2015-09-08 上午12.45.48.png

首先觀察某篇博文兑宇,發(fā)現(xiàn)有以下幾類關(guān)鍵信息:

  1. 博文題目:太對不起了碍侦,被坐骨神經(jīng)折騰了一晚。
  2. 發(fā)表日期:2008-08-30 19:14:19
  3. 博文標(biāo)簽:纏中說禪 健康
  4. 博文分類:纏中說禪
  5. 博文本身:隶糕。瓷产。。枚驻。濒旦。。再登。尔邓。晾剖。。

由于博文較為復(fù)雜梯嗽,只能使用BeautifulSoup進行解析齿尽。參考eautiful Soup 4.2.0 文檔
以上1至4均使用find函數(shù):
find( name , attrs , recursive , text , **kwargs )
由于此4類的標(biāo)簽(tag)中的屬性(attribute)較為特殊灯节,所以均以此搜索循头。

class sinaBlogContentTool:

def __init__(self,page):
    self.page = page

def parse(self):
    '''解析博客內(nèi)容'''
    soup = BeautifulSoup(self.page)
    
    self.title = soup.body.find(attrs = {'class':'titName SG_txta'}).string
    
    self.time = soup.body.find(attrs = {'class':'time SG_txtc'}).string
    self.time = self.time[1:-1]
    print u"發(fā)表日期是:", self.time, u"博客題目是:", self.title
    
    self.tags = []
    for item in soup.body.find(attrs = {'class' : 'blog_tag'}).find_all('h3'):
        self.tags.append(item.string)
    
    self.types = u"未分類"
    if soup.body.find(attrs = {'class' : 'blog_class'}).a:
        self.types = soup.body.find(attrs = {'class' : 'blog_class'}).a.string

    self.contents = []
    self.rawContent = soup.body.find(attrs = {'id' : 'sina_keyword_ad_area2'})

    for child in self.rawContent.children:
        if type(child) == NavigableString:
            self.contents.append(('txt', child.strip()))
        else:
            for item in child.stripped_strings:
                self.contents.append(('txt', item))
            if child.find_all('img'):
                for item in child.find_all('img'):
                    if(item.has_attr('real_src')):
                        self.contents.append(('img', item['real_src']))

博文本身比較復(fù)雜,因為不僅包含文字炎疆,還有圖片卡骂。所以使用children屬性,可以遍歷Tag或BeautifulSoup對象的子項形入。
如果子項為NavigableString對象(即為字符串)全跨,則直接保存它本身。
否則亿遂,使用stripped_strings屬性浓若,將子項中的所有NavigableString對象均保存下來。同時崩掘,判斷該子項中是否有屬性為‘img’的Tag對象七嫌,若有,則取該Tag的real_src屬性保存下來苞慢。
這樣文字和圖片都獲取到了诵原。

最后,在Spider類中使用run函數(shù)將以上內(nèi)容都串起來:

def run(self):
    self.getTypeNum()
    i = raw_input(u"請輸入需要下載的類別ID(如需要下載類別為“全部博文”類別請輸入0):")
    page0 = self.getPage(self.allType[int(i)][0])
    pageNum = self.getPageNum(page0)
    urlHead = self.allType[int(i)][0][:-6]
    typeName = self.allType[int(i)][1]
    typeBlogNum = self.allType[int(i)][2]
    if typeBlogNum == '0':
        print u"該目錄為空"
        return
    self.mkdir(typeName)
    for j in range(pageNum):
        print u"------------------------------------------正在下載類別為", typeName, u"的博客的第", str(j+1), u"頁------------------------------------------"
        url = urlHead + str(j+1) + '.html'
        page = self.getPage(url)
        blogList = self.getBlogList(page)
        print u"本頁共有博客", len(blogList), u"篇"
        for item in blogList:
            print u"正在下載博客《", item[1], u"》中……"
            self.saveBlogContent(typeName, item[0])
    print u"全部下載完畢"

以下是成果展示:

屏幕快照 2015-09-08 上午1.03.03.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挽放,一起剝皮案震驚了整個濱河市绍赛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辑畦,老刑警劉巖吗蚌,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纯出,居然都是意外死亡蚯妇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門暂筝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箩言,“玉大人,你說我怎么就攤上這事焕襟≡墒眨” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵鸵赖,是天一觀的道長务漩。 經(jīng)常有香客問我拄衰,道長,這世上最難降的妖魔是什么饵骨? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任翘悉,我火速辦了婚禮,結(jié)果婚禮上居触,老公的妹妹穿的比我還像新娘镐确。我一直安慰自己,他們只是感情好饼煞,可當(dāng)我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诗越,像睡著了一般砖瞧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嚷狞,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天块促,我揣著相機與錄音,去河邊找鬼床未。 笑死竭翠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的薇搁。 我是一名探鬼主播斋扰,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼啃洋!你這毒婦竟也來了传货?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤宏娄,失蹤者是張志新(化名)和其女友劉穎问裕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孵坚,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡粮宛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了卖宠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巍杈。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逗堵,靈堂內(nèi)的尸體忽然破棺而出秉氧,到底是詐尸還是另有隱情,我是刑警寧澤蜒秤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布汁咏,位于F島的核電站亚斋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏攘滩。R本人自食惡果不足惜帅刊,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漂问。 院中可真熱鬧赖瞒,春花似錦、人聲如沸蚤假。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磷仰。三九已至袍嬉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灶平,已是汗流浹背伺通。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逢享,地道東北人罐监。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像瞒爬,于是被迫代替她去往敵國和親弓柱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容