您的位置:首页 > 编程语言 > Python开发

[PYTHON]-用Scrapy爬虫遍历百度贴吧,本地保存文字版【PART 2】

2015-10-24 11:11 806 查看
这里紧接着上一节的内容讲:

上一节在这里:/article/3619553.html

上次我们定义了item.py\pipelines.py\settings.py,了解了布隆过滤。今天我们定义重头戏:定义蜘蛛

我们首先在脑海里回想一下我们的目标:

抓取所有的《中山大学吧》帖子标题+内容

我们先来到百度贴吧的中山大学主页。



我们看到,主页的URL是:

http://tieba.baidu.com/f?ie=utf-8&kw=%E4%B8%AD%E5%B1%B1%E5%A4%A7%E5%AD%A6&pn=1

这个怎么解析呢?首先,ie=utf-8是解码格式,kw=%E4%B8%AD%E5%B1%B1%E5%A4%A7%E5%AD%A6是“中山大学”编码后的字符串

pn=1是指当前的主页(mainpage)从第几个帖子开始显示,经过实验,pn可以等于任何一个正整数,也就是说当pn=25 主页就从第25个帖子开始显示,pn=44就从第44个帖子开始显示。

好。我们先想一下我们的结构:



【注意】所有的url都要先放入布隆过滤器,如果重复就不再抓取!

OK,我们想好了结构,就用chrome先来看主页的源代码,找找如何执行:主页==》某个帖子。

我们搜索帖名:

然后看到



我们分析,发现这是一个
class="j_th_tit "


的标签a,其包含的@href性质,就是我们要抓的网页。

所以用XPath表示是:

[code]sel.xpath('//a[@class="j_th_tit "]/@href').extract()


由于这里抓到的只是tieba.baidu.com/后面的一部分,所以要加回前面的域名。

[code]        sel=Selector(response)
        sites=sel.xpath('//a[@class="j_th_tit "]/@href').extract()
        for site in sites:
            urls = "http://tieba.baidu.com"+site


现在我们进入了帖子!

问题在于:如何抓帖子的content ? 这个我们来看看具体帖子的源代码!



大家看到,黑色的是楼主发的文字。

红色的是独一无二的标签,匹配的是整个页面所有人发的内容。

我们只要其中“黑色的文本文字”,于是我们就这样抓取吧!

[code]    contentList=site.xpath('//div[@class="d_post_content j_d_post_content "]/text()').extract()


然后对于每个内容,我们输出到item里:

[code]    for floor in contentList:
        utfcontent=floor.encode('utf-8')
        item["content"] += utfcontent
        item["content"] += "\n"


好了。然后我们想想,如果一个帖子有很多页,怎么抓每一页的东西呢?

光是想想不出来的,我们来看看具体的帖子:





有办法了,我们就利用这个”下一页“按钮去抓下一页!

截取的源代码:




我们可以看到,网址是a标签的href属性,而这个属性的父标签是
<li>
,标签性质class里含有pager关键字。

经过多个页面的观察发现,pager这个标签可以是_theme_4也可能是_theme_5 之类,不确定,所以我们用contains进行模糊匹配

[code]  urlList=site.xpath('//li[contains(@class,"l_pager")]/a/@href').extract()


我们之前说过,要用bloom fliter查重!所以我们这样处理:

[code]        for t in urlList:
            if (self.bf.is_element_exist("http://tieba.baidu.com"+t)==False): #判断是否已经保存过? 
                yield Request("http://tieba.baidu.com"+t,callback=self.parse_inPage)
            else:
                continue


第三个问题:如何获取标题?

我们说过,文档名就是贴子标题,这个要如何获取呢?

老办法了,抓源代码,看标签,用Xpath:




我们这里用

[code]       titleList=site.xpath('//h3[contains(@class,"core_title_txt")]/@title').extract()


然后对于每个title,存到item里

[code]      for t in titleList:
            title=t.encode('utf-8')
            item["title"] += title            
        yield item


太容易了!

最后一个问题:如何更新主页?

为什么要更新主页呢?我们知道,一个主页如果pn=0不更新的话,最多也就抓50个帖子

我可是要把百度贴吧爬下来的男人!怎么能容忍这个缺陷呢!

于是,我们做个计数器:每进去10次帖子,就把主页更新一遍。为了方便呢,我们把mainpage作为类的成员,计数器也做成类的成员。

然后我们只要,每次self.count%10==0,我们就把mainpage更新,pn=str(self.count)。

实现如下:

[code]        self.count+=1
        if self.count%10==0:
            self.mainpage="http://tieba.baidu.com/f?kw=%E4%B8%AD%E5%B1%B1%E5%A4%A7%E5%AD%A6&ie=utf-8&pn="+str(self.count)
        yield Request(self.mainpage,callback=self.parse_mainPage)


大功告成!

到了这一步,我们基本把蜘蛛文件的大部分搞定了,只需要回到pipelines看看之前没有认真完成的管道:

我们知道,我们在这个文件里做的,content和title定义成是utf-8编码的字符串,所以我们要做的

1.解码

2.write

很简单了是不是!

NO!

注意!!!! 每个帖子从第二页开始,标题格式都是这样的“回复:XXXXX”

如果简单地这样保存会使你的每个帖子有两个文件,文件名分别是【 帖名.txt】、【回复:帖名.txt】

我可是要把百度贴吧爬下来的男人!怎么能容忍这个缺陷呢!

好,我们要怎么做?

我们让key=”回复:”

我们用str的rfind(key)函数,返回字符串匹配key的最后一个下标,然后用split(key),取出第二部分,就大功告成了!代码如下

pipelines.py:

[code]class SysuPipeline(object):
    def __init(self):
        pass

    def process_item(self, item, spider):
        key='回复:'
        index=item["title"].rfind(key)     #rfind() return the last index!

        if(index==-1):                     #not found!
            title=item["title"].decode('utf-8')  #item['title'] has been encoded to utf8
        else:
            t=item["title"].split(key)[1]
            title=t.decode('utf-8')

        path='D:\\guagua\\SYSU\\DATA\\'+ title +'.txt'
        output = open(path,'a')
        output.write(item["content"])
        output.close()
        return item


我们蜘蛛要设置两个解析函数:一个parse_mainPage负责处理主页,一个parse_inPage负责处理帖子。

parse_mainPage的作用:取出帖子的地址!

parse_inPage的作用:抓取title\content,然后抓取下一页,直到抓完。

蜘蛛完善好,把上面说到的几个Xpath放进去

对了,别忘了把bloom fliter.py保存到本地,【PART1】有源代码

最后贴上蜘蛛代码:

[code]from scrapy.spider import Spider  
from scrapy.http import Request  
from scrapy.selector import Selector  
from SYSU.items import SysuItem
from SYSU.bloomfliter import BloomFilter

class SYSUSpider(Spider):
    name = 'SYSUSpider'
    allowed_domains=["tieba.baidu.com"]

    def __init__(self, name=None, **kwargs):
        if name is not None:
            self.name = name
        elif not getattr(self, 'name', None):
            raise ValueError("%s must have a name" % type(self).__name__)
        self.__dict__.update(kwargs)
        if not hasattr(self, 'start_urls'):
            self.start_urls = []

        self.bf=BloomFilter(0.0001,100000)
        self.mainpage="http://tieba.baidu.com/f?kw=%E4%B8%AD%E5%B1%B1%E5%A4%A7%E5%AD%A6&ie=utf-8"
        self.count=0

    def start_requests(self):
        yield Request(self.mainpage,callback=self.parse_mainPage)

    def parse_inPage(self,response):
        sel=Selector(response)
        site=sel
        url=''
        item=SysuItem()
        item["title"]=''
        item["content"]=''
        c=site.xpath('//link[@rel="canonical"]/@href').extract()
        currentUrl=c[0]
        self.bf.insert_element(currentUrl)

        contentList=site.xpath('//div[@class="d_post_content j_d_post_content "]/text()').extract() 
        for floor in contentList:
            utfcontent=floor.encode('utf-8')
            item["content"] += utfcontent
            item["content"] += "\n"

        titleList=site.xpath('//h3[contains(@class,"core_title_txt")]/@title').extract()
        for t in titleList:
            title=t.encode('utf-8')
            item["title"] += title            
        yield item        

        urlList=site.xpath('//li[contains(@class,"l_pager")]/a/@href').extract()
        for t in urlList:
            if (self.bf.is_element_exist("http://tieba.baidu.com"+t)==False):  # reduce a /
                yield Request("http://tieba.baidu.com"+t,callback=self.parse_inPage)
            else:
                continue

        self.count+=1
        if self.count%10==0:
            self.mainpage="http://tieba.baidu.com/f?kw=%E4%B8%AD%E5%B1%B1%E5%A4%A7%E5%AD%A6&ie=utf-8&pn="+str(self.count)
        yield Request(self.mainpage,callback=self.parse_mainPage)

    def parse_mainPage(self,response):
        sel=Selector(response)
        sites=sel.xpath('//a[@class="j_th_tit "]/@href').extract()
        for site in sites:
            urls = "http://tieba.baidu.com"+site
            if(self.bf.is_element_exist(urls)==False):
                yield Request(urls,callback=self.parse_inPage)
            else:
                continue

#      cmd: guagua\SYSU>scrapy crawl SYSUSpider


然后呢?

然后我们就等!

由于网速有限,我们几乎不可能高效率地爬下整个百度贴吧,只能爬某一两个吧。

有人会问了,露珠我是新手,感觉我很难一次写出这么长的代码啊

晕,我也是新手,所以我的建议如下:

一步步完成程序、由低到高实现功能!

什么意思?

本例:我是这样做的:

步骤一:把http://tieba.baidu.com/p/4117846868 这样的页面,单页面的内容爬下来

步骤二:升级代码,使得代码能自动把上述url的第二页、第三页…都抓下来

步骤三:升级item.py,标题过滤“回复:”关键字。

步骤四:升级代码,加入bloom fliter.py

步骤五:升级代码,使得“访问主页”变成第一步,改用start_request()

步骤六:升级代码,加入parse_inPage和parse_mainpage,加入计数器用于更新主页

建议!

一步一步学习,遇到不懂就百度

基本了解就开始实践,不要永远卡在理论阶段!

本程序可提升部分:

1. 多线程执行任务

2. 分布式抓取

3. 优化文本布局(这个最简单hhhh)

以后有提升会更新的,现在我要试着去爬你懂磁力链接了hhhhhhhh

(误 新世界的大门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: