Python爬虫Csdn系列II
By 白熊花田(http://blog.csdn.net/whiterbear) 转载需注明出处,谢谢。
说明:
在上一篇文章中,我们已经知道了只要将程序伪装成浏览器就能访问csdn网页。在这篇文章中,我们将设法获取某个csdn用户的所有文章的链接。
分析:
打开一个某一个的csdn用户的的专栏,可以选择目录视图(如:http://blog.csdn.net/whiterbear?viewmode=contents)和摘要视图(比如:http://blog.csdn.net/whiterbear?viewmode=list)。两个视图都可以显示用户的文章列表。
注意:这里我们选择摘要视图,不要选择目录视图,文章最后会解释为什么。
打开摘要视图查看网页源代码,我们发现,在id为’article_list’的div中,每一个子div都代表着一篇文章,如图:
每一个子div中都包含一篇文章的标题,链接,阅读次数,是否为原创,评论数等信息,我们只需要取出标题和链接就够了。如何取出不难,学过正则表达式应该都会。我们再使用个数组将界面中所有的文章名及其链接保存即可。
这里需要注意的是,如果博客有分页怎么办,我们还需要获取分页的下一页中的文章的链接?
我尝试了两种方法,第一种方法是设定一个article_list字典,字典成员为‘下一页链接和是否已经被访问标识键值对’,初始放入首页链接,每处理一个界面时将该链接的值设为访问,之后查找下一页的链接,如果其不在字典里,就将其加入并设访问标识为0。
比如初始字典为article_list={‘/pongba/article/list/1’:False},在处理/pongba/article/list/1这个界面时设值为True,此时又发现了/pongba/article/list/2和/pongba/article/list/3。此时,我们判断字典(has_key())中是没有这两个键的,就加入,并设其值为False。之后遍历(keys())字典,如果有值为0的链接,则访问该链接,重复。
第二种方法:在分页的html代码中给出了分页的页数,我们提取出页数pagenum,结合/pongba/article/list/num,num为页数,值为[1,pagenum]。通过这个链接即可取出该作者的所有文章了。
我代码中采用了第二种方法,第一种方法试了,也可以。
代码介绍:
CsdnArticle类(article.py),封装成一篇文章必须的东西,便于保存和访问一个文章的属性。
我重写了__str__()方法,便于输入。
#-*- coding:utf-8 -*- class CsdnArticle(object): def __init__(self): #作者 self.author = '' #博客文章名 self.title = '' #博客链接 self.href = '' #博客内容 self.body = '' #字符串化 def __str__(self): return self.author + '\t' + self.title + '\t' + self.href + '\t' + self.body
CsdnCrawler类。封装了爬取csdn博客所有链接的操作。
#-*- coding:utf-8 -*- import sys import urllib import urllib2 import re from bs4 import BeautifulSoup from article import CsdnArticle reload(sys) sys.setdefaultencoding('utf-8') class CsdnCrawler(object): #默认访问我的博客 def __init__(self, author = 'whiterbear'): self.author = author self.domain = 'http://blog.csdn.net/' self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36'} #存储文章对象数组 self.articles = [] #给定url,得到所有的文章lists def getArticleLists(self, url= None): req = urllib2.Request(url, headers=self.headers) response = urllib2.urlopen(req) soup = BeautifulSoup(''.join(response.read())) listitem = soup.find(id='article_list').find_all(attrs={'class':r'list_item article_item'}) #链接的正则表达式,可以匹配链接 href_regex = r'href="(.*?)"' for i,item in enumerate(listitem): enitem = item.find(attrs={'class':'link_title'}).contents[0].contents[0] href = re.search(href_regex,str(item.find(attrs={'class':'link_title'}).contents[0])).group(1) #我们将获取的一篇文章信息封装成一个对象,然后存入数组中 art = CsdnArticle() art.author = self.author art.title = enitem.lstrip() art.href = (self.domain + href[1:]).lstrip() self.articles.append(art) def getPageLists(self, url= None): url = 'http://blog.csdn.net/%s?viewmode=list'%self.author req = urllib2.Request(url, headers=self.headers) response = urllib2.urlopen(req) soup = BeautifulSoup(''.join(response.read())) num_regex = '[1-9]\d*' pagelist = soup.find(id='papelist') self.getArticleLists(url) #如果该作者博客多,有分页的话 if pagelist: pagenum = int(re.findall(num_regex, pagelist.contents[1].contents[0])[1]) for i in range(2, pagenum + 1): self.getArticleLists(self.domain + self.author + '/article/list/%s'%i) def mytest(self): for i,url in enumerate(self.articles): print i,url def main(): #可以将pongba换成你的博客名,也可以不填,为空,这样默认是访问我的博客 csdn = CsdnCrawler(author='pongba')#'pongba' csdn.getPageLists() csdn.mytest() if __name__ == '__main__': main()<span style="font-family:Verdana;font-size:18px;"> </span>
结果:
输出了126条数据。
选择摘要视图的解释:当某用户文章多有分页时,访问目录视图界面中的下一页链接时会跳转到摘要视图的下一页链接中。我这么说你可能不太明白,举个例子吧。
我使用刘未鹏学长(我崇拜的)的博客为例子吧,地址:http://blog.csdn.net/pongba。他的文章很多,有分页。选择他界面中的目录视图后,翻到分页链接出。如下图:
其中的分页链接值为:
可以看出下一页的链接为:http://blog.csdn.net +/pongba/article/list/2.
当我们在浏览器输入这个网址回车后是出现这个结果的:
可以看到第一篇文章为“斯托克代尔悖论与底线思考法”
然而,当我们使用程序打开的结果却是:
而这个结果却和摘要视图的第二页结果一样:
所以,如果你试图用程序使用http://blog.csdn.net/pongba/article/list/2这个链接访问,得到的结果却并不是目录视图的结果。我没能理解为什么,纠结了好久程序为什么出错了,后来换成摘要视图了。
未完待续。