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

来源:互联网 发布:网络安全法宣传周展板 编辑:程序博客网 时间:2024/06/05 05:34

来自某SYSU中山不放假大学,初级新手的python自学之路啊。。。泪目,做一个Scrapy教程方便跟我一样的新人吧,整天百度太累人了!

之前用python的requests模块做了爬糗百、贴吧(单帖)、中大教务系统的选课结果查询的小project,其实运用的都是最基本的爬虫,在还是再强调一下requests模块真的比urllib好用多了,真心推荐大家去学习一下,可以节约你背urllib函数的很大功夫。
第一个糗百系列的教程笔记已经上传到本博客,贴吧的就不单独写了,实现起来和糗百差不多;

中大教务系统的实验教程有空补上,最近玩爬虫玩得太High落下不少功课Orz 谅解

废话不说,开始我们的教程,这次我们尝试学习Scrapy(小刮刮)来爬整个吧。不得不说Scrapy是一个对新手不太友好的东东。。。无论是安装还是学习,而且上手直接看官方教程会比较痛苦,不停百度谷娘才能慢慢领悟。。大家加油

本文章可以作为读者对Scrapy的入门兴趣引导,但主要建立在读者已有Scrapy基础、或者有耐心在边看本文边百度相关知识的情形下学习和阅读
先看成果
run了大约两个小时,冰山一角,惊鸿一瞥?
爬虫结果一览
按照校园网的渣网速,大概一小时10000个帖子左右,假设平均每个帖子两页,就是20000页,大概也就是几十万楼了。
里面是帖子的每层楼的留言(目前只是初步抓取,图片和格式方面没有处理。后期再完善)
这里写图片描述
这是卤煮睡了一觉起来看的结果:90197个帖子。

这里写图片描述
之所以停在这个数字是因为…之前还在Debug,当时只是随便给了bloom fliter十万的空间。

如果单靠这台电脑爬的话,《中山大学吧》大约两百万的帖子,大约7、8天能爬完。
限制因素:主要是网速。。。。
(还远远达不到单进程的极限啊 啊啊啊 啊!!!我要升级校园网!)


实验目的
不重复地爬取《中山大学百度贴吧》的帖子内容
每个帖子要从第一页爬到最后一页,需要遍历每层楼
以文档形式把所有帖子的文字内容保存到本地

实验要求
保存到本地的文档,每个文档的文档名就是帖子名,文档内容就是帖子文字内容

实验环境:
windows 8.1 (为了scrapy我找教程把用户名改成英文了..)
python 2.7.*
Scrapy
Iphthon
pywin32

实验相关知识:
网络爬虫
Python爬虫库
Scrapy库
XPath表达式
HTML/XML入门知识
python-迭代器和产生器generator
bloom fliter 布隆过滤器
(可能需要)正则表达式

实验步骤:
1.安装所需环境。

2.先用cmd命令cd进入想建立project的目录,用下列命令建立project “SYSU”

scrapy startproject SYSU

3.Scrapy自动建立一系列的文件和文件夹:

SYSU/    scrapy.cfg    SYSU/        __init__.py        items.py        pipelines.py        settings.py        spiders/            __init__.py            ...

解释:

scrapy.cfg是配置文件,不用管它
items.py是item项目,作为爬虫的输出对象,需要自行编辑
pipelines.py是管道,需要自行编辑
settings.py是用户可以更改的设定文件,需要自行编辑
最后,spiders文件夹里,注意加上自定义的蜘蛛,需要自行编辑!

4.编辑items文件
这里考虑:我们需要保存什么呢?
根据上面的实验要求,无非两个:1.帖子标题 2.帖子内容
好,所以我们在这里这样写:

import scrapyclass SysuItem(scrapy.Item):    # define the fields for your item here like:    title = scrapy.Field()    content =  scrapy.Field()    pass

轻松搞定。注意,Field()是暂且不确定的Python字典,运行后才有一个固定的数据结构,这里仅仅做个字典的“壳子”而已。

5.编辑pipelines.py
pipeline,英文管道也,顾名思义就是输出文本的通道。我们用这个文件来做文档输出。

这一步其实很困难。。我们还没写最重要的蜘蛛,不知道具体要输出什么,但我们根据item.py知道:
item只有两个属性,title和content..所以我们假设大概就是这样的(可以先写不严格的伪代码):

class SysuPipeline(object):        def process_item(self, item, spider):        path='D:\\guagua\\SYSU\\DATA\\'+item["title"] +'.txt'        output = open(path,'a')        output.write(item["content"])        output.close()        return item

好,然后我们直接下一步:

6。编辑setting.py
仔细看,这个文件其实已经把所有的“开关”都定义好了,然后每一行都被注释了。
我们要用只需要把对应的#号去掉
33行的地方,有:

#Disable cookies (enabled by default)COOKIES_ENABLED=False

我们把COOKIES_ENABLED=False的#号去掉,避免重复访问因为COOKIE被封(这个开关默认是开的)

重点! 64行的井号去掉(启用开关),把这个“管道开关”设定名称并打开

ITEM_PIPELINES = {    'SYSU.pipelines.SysuPipeline': 300,}

注意这里的管道名:SysuPipeline 是你第5步设定的名字哦!
大功告成,进入正题:

7.编辑自己的蜘蛛!
所有蜘蛛都必须继承一个基类,这个基类蜘蛛是 scrapy.Spider.spider
每个蜘蛛都有name,这是区分每只蜘蛛的独特性质
一般来说,初始的除了Name,还有allowed_domains(允许域名)、start_urls
这些都是很伪代码的东西,具体可以百度。

每个蜘蛛都有Request命令,必须有的参数表是:
Request(url, callback=xxxx)
callback是回调函数(百度),一般回调函数都是蜘蛛里的(类似parse的)解析函数。调用callback回调时,传了一个response参数给回调函数

每个蜘蛛默认调用的是parse解析函数,解析函数可以自定义,在这里我们自定义了回调函数也一样,传入self和response,返回**必须是**item类(见上4)或者/和 Request回调

注意!这里的返回可以指return和yield(常用!)
yield的使用详情谷歌。简单来说,就是返回一个产生器generator,这个产生器它不是返回一个立即的列表值,而是一个数据结构,它只有被for迭代调用的时候,才逐步执行,其他时刻理解成yield返回一个暂停的值!

【布隆过滤】百度!
Bloom filter 是由 Howard Bloom 在 1970 年提出的二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员。如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中。因此Bloom filter具有100%的召回率。这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况,可见 Bloom filter 是牺牲了正确率和时间以节省空间。

本质就是利用散列函数,避免重复访问同一个url。优点:快速,缺点:散列碰撞
这里借用了bloom fliter的python实现代码,感谢原作者。

把代码贴在这里,原理理解就行,初学者主要学会运用,最后四行是范例,通俗易懂。

#!/usr/local/bin/python2.7#coding=gbk'''Created on 2012-11-7@author: palydawn'''import cmathfrom BitVector import BitVectorclass BloomFilter(object):    def __init__(self, error_rate, elementNum):        #计算所需要的bit数        self.bit_num = -1 * elementNum * cmath.log(error_rate) / (cmath.log(2.0) * cmath.log(2.0))        #四字节对齐        self.bit_num = self.align_4byte(self.bit_num.real)        #分配内存        self.bit_array = BitVector(size=self.bit_num)        #计算hash函数个数        self.hash_num = cmath.log(2) * self.bit_num / elementNum        self.hash_num = self.hash_num.real        #向上取整        self.hash_num = int(self.hash_num) + 1        #产生hash函数种子        self.hash_seeds = self.generate_hashseeds(self.hash_num)    def insert_element(self, element):        for seed in self.hash_seeds:            hash_val = self.hash_element(element, seed)            #取绝对值            hash_val = abs(hash_val)            #取模,防越界            hash_val = hash_val % self.bit_num            #设置相应的比特位            self.bit_array[hash_val] = 1    #检查元素是否存在,存在返回true,否则返回false     def is_element_exist(self, element):        for seed in self.hash_seeds:            hash_val = self.hash_element(element, seed)            #取绝对值            hash_val = abs(hash_val)            #取模,防越界            hash_val = hash_val % self.bit_num            #查看值            if self.bit_array[hash_val] == 0:                return False        return True    #内存对齐        def align_4byte(self, bit_num):        num = int(bit_num / 32)        num = 32 * (num + 1)        return num    #产生hash函数种子,hash_num个素数    def generate_hashseeds(self, hash_num):        count = 0        #连续两个种子的最小差值        gap = 50        #初始化hash种子为0        hash_seeds = []        for index in xrange(hash_num):            hash_seeds.append(0)        for index in xrange(10, 10000):            max_num = int(cmath.sqrt(1.0 * index).real)            flag = 1            for num in xrange(2, max_num):                if index % num == 0:                    flag = 0                    break            if flag == 1:                #连续两个hash种子的差值要大才行                if count > 0 and (index - hash_seeds[count - 1]) < gap:                    continue                hash_seeds[count] = index                count = count + 1            if count == hash_num:                break        return hash_seeds    def hash_element(self, element, seed):        hash_val = 1        for ch in str(element):            chval = ord(ch)            hash_val = hash_val * seed + chval        return hash_val'''#测试代码#bf = BloomFilter(0.001, 1000000)#element = 'palydawn'#bf.insert_element(element)#print bf.is_element_exist('palydawn')'''

好,理解完bloom fliter,我们下一PART将进入正式的蜘蛛编写:

点击下方链接可进入下一篇。

喜欢的话点个赞哦!

(TO BE CONTINUE!)

0 0