使用python下载新浪博客

7个月前的一篇todo-list:一个下载新浪博客工具的to-do list
今天终于可以说是完工了。

代码链接

主要的技术点:

  1. 使用urllib和urllib2获取网页内容
  2. 使用BeautifulSoup和re来解析网页内容

编码思路:

一、获取博文列表

我想要下载的目标博客:缠中说禅的博客
分析此博客,发现点击“博客目录”后可获取较调理的信息:

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

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

发现以下几点:

  1. 左侧有博文分类,可通过此处获取感兴趣的分类
  2. 下方有当前分类的所有博文所占的页数,可通过此处获得总工作量

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函数中,获取网页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.

在之后解析每篇博客内容时,re就无能为力了,我只好去使用BeautifulSoup,但是在前期我却是参考别的文章使用了正则表达。

在getPageNum函数中使用re来获取了当前分类的总页数。

然而我们刚开始的时候并不知道要选择哪个分类,所以要将这些信息显示出来供用户选择。

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。在该函数中获取各分类对应的URL。

现在的流程梳理下就是这样的:

  1. 程序获取所有的博文分类
  2. 用户选择感兴趣的分类
  3. 程序获取该分类的URL和页数
  4. 程序获取并解析每篇文章(下一章)

二、解析文章

首先会根据分类和页数,得到具体某一页的博文列表的URL。具体规则上面已提到。然后需要将此页中的所有博客的URL解析出来。

def getBlogList(self,page):
    '''获取一页内的博客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"的文件夹已经存在"
        return False
    else:
        print u"正在创建名为", path, u"的文件夹"
        os.makedirs(path)

def saveBlogContent(self,path,url):
    '''保存url指向的博客内容'''
    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("标签:")
        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"下载成功"

接下来就是解析博客,保存内容至本地。其中创建文件名时需要注意“/\”此类符号,我的做法是将符号变为文字“斜杠”。

优于解析博客内容较为复杂,我创建一个class专门解析。

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

首先观察某篇博文,发现有以下几类关键信息:

  1. 博文题目:太对不起了,被坐骨神经折腾了一晚。
  2. 发表日期:2008-08-30 19:14:19
  3. 博文标签:缠中说禅 健康
  4. 博文分类:缠中说禅
  5. 博文本身:。。。。。。。。。。

由于博文较为复杂,只能使用BeautifulSoup进行解析。参考eautiful Soup 4.2.0 文档
以上1至4均使用find函数:
find( name , attrs , recursive , text , **kwargs )
由于此4类的标签(tag)中的属性(attribute)较为特殊,所以均以此搜索。

class sinaBlogContentTool:

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

def parse(self):
    '''解析博客内容'''
    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"发表日期是:", 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']))

博文本身比较复杂,因为不仅包含文字,还有图片。所以使用children属性,可以遍历Tag或BeautifulSoup对象的子项。
如果子项为NavigableString对象(即为字符串),则直接保存它本身。
否则,使用stripped_strings属性,将子项中的所有NavigableString对象均保存下来。同时,判断该子项中是否有属性为‘img’的Tag对象,若有,则取该Tag的real_src属性保存下来。
这样文字和图片都获取到了。

最后,在Spider类中使用run函数将以上内容都串起来:

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容