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

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

2015-10-24 00:52 866 查看
来自某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”

[code]scrapy startproject SYSU


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

[code]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.帖子内容

好,所以我们在这里这样写:

[code]import scrapy

class 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..所以我们假设大概就是这样的(可以先写不严格的伪代码):

[code]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行的地方,有:

[code]#Disable cookies (enabled by default)
COOKIES_ENABLED=False


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

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

[code]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实现代码,感谢原作者。

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

[code]
#!/usr/local/bin/python2.7
#coding=gbk
'''
Created on 2012-11-7

@author: palydawn
'''
import cmath
from BitVector import BitVector

class 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!)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: