python网络爬虫(三)

在第二篇中我们介绍从响应中提取结构化数据,那接下来就是需要保存数据。对于html网页(主要用于缓存)和结构化数据的保存,我选择了MySQL, 当然也可以使用NoSQL的产品, 比如mongodb;对于网页中提取出的url,我们可以保存到本地的文件中,也可以保存到MySQL或者mongodb中等,但为了高效,我选择了内存数据库redis,相对来说redis的永久存储性能要弱于MySQL等产品,但他的查询非常的高效。

python的MySQL的接口安装:

pip install MySQL-python

python的redis接口:

pip install redis-py

python接口的布隆过滤器:

pip install pybloom

bloom filter是用来过滤重复的url的,我们从网页中提出的url,有很多是我们已经抓取过的,对于这部分url就要去重,那么bloom filter就是最好的选择,因为它的查重的速度是O(1)。也就是说,你已经抓取了100万条url,现在给你一条新的url,你要判断是否抓取过。采用最普通的方法就是for循环,这需要O(n)的时间;再进一步,我们可以选择集合,因为python的集合是用hash的,但是有一个问题,要把100万条url都放到集合中,这是非常消耗内存的,如果有两百万,三百万,甚至几千万呢?一般的计算机是承受不住的。

而bloom filter查重是一个常数时间,而且,他内存消耗率很低,这不正是我们想要的吗?但是它也有一个缺点,会存在误判的情况。就是说,bloom filter瞄过一眼的肯定不会判断错误,但是它可能会把不在bloom filter中的url判断会存在。这种错误率,也称为‘假阳性’,是可以计算出来的。我们可以控制在万分之一左右,这种错误率是可以接受的。

bloom filter有一个公式可以计算需要的内存。具体可以去查询相关的资料,这里不做多介绍。

假设html我们需要保存的网页(unicode类型), item是我们需要保存的结构化数据(dict类型), urls是我们需要保存的url列表(list类型)。

import zlib
import MySQLdb
class MySQLConnect(object):
    """
    建立到MySQL的连接
    """
    #我们先建立到mysql的连接
    def __init__(self, host=host, password=password, 
                 username=username, database=database,
                 charset=charset):
        self.connect = MySQLdb.connect(host=host, user=username, 
                                       passwd=password, db=database
                                       charset=charset)
        self.cousor = self.connect.cursor()

    def save(self, item, html):
        result_item = (item(字段1), item(字段2), item(字段3)...)
        self.cousor.execute("""insert into item_table
                               (字段1, 字段2,.....) 
                               values (%s, %s,....)""", result_item)

        #首先将html进行压缩,在进行存储
        compress_html = zlib.compress(html.encode('utf-8'))
        html_result = (url, compress_html)
        self.cousor.execute("""insert into html_table
                               (字段1, 字段2) 
                               values (%s, %s)""", html_result)
        self.connect.commit()

代码非常简单,其中我虚构了两张表,item_table和html_table,用来存储结构化数据和html网页。当然我们也可以把响应的头部也存储起来,以待更新数据时向服务验证网页是否发生改变。

而对于urls列表的存储我们采用下面的方法:

import redis
import urlparse
import json

from pybloom import BloomFilter
class MyRedis(myconnection.RedisConnect):
    """
    redis
    """
    def __init__(self, host=host, port=port, db=db):
        self.pool = redis.ConnectionPool(host=host, port=port, db=db)
        self.r = redis.StrictRedis(connection_pool=self.pool)

        try:
            print u'正在初始化,请稍后.....'
            f = open('/home/root/bloomfilter.txt')#尝试打开保存bloomfilter的文件
        except IOError:
            print 'create a new bloomfilter without file'
            #如果打开失败,说明不存在这个文件,就重新创建一个bloom filter
            self.bloomfilter = BloomFilter(capacity=1000000, 
                                           error_rate=0.00001)
        else:
            print u'从文件中加载bloom filter'
            self.bloomfilter = BloomFilter.fromfile(f)

    def bloom_filter_url(self, urls, baseurl):
        """
        将unbloom_url_queue这个队列中的url过滤,取出未抓取的url到url_queue中
        baseurl用于讲urls列表中的相对url补全为绝对url
        """
            parse_url = urlparse.urlparse(baseurl)
            #提取domain,比如‘https://www.example.com’
            domain = '://'.join((parse_url.scheme, parse_url.netloc))
            new_list = []
            for url in urls:                    
                #这一步就可以排除所有不在domain下的url,比如domain是www.example.com
                #那么‘https://www.qweasd.com’这个域名以及该域名下的所有url就会被排除
                if (parse_url.netloc not in url) and (parse_url.scheme in url):
                    pass
                else:
                    #这一步判断url是否为完整的url
                    if domain not in url:
                        url = ''.join([domain, url])
                    if '#' in url:
                        #如果url中存在#号,就删除它
                        url = urlparse.urldefrag(url)[0]
                        #url中可能存在转义字符,统一转化为原始字符,比如将%7e转化为~
                        url = urllib.unquote(url)
                    if not self.bloomfilter.add(url):#判断url是否在bloomfilter中
                        new_item = json.dumps({'url': url, 
                                            'base_url': baseurl})
                        new_list.append(new_item)
            else:
                self.r.rpush('url_queue', new_list)

上面的代码涉及了pybloom和redis的api接口操作,建议直接去看官方的文档。

到目前为止,我们已经会进行网页下载,提取结构化数据,保存网页和数据,去重url并进行保存。一个爬虫完整的步骤已经结束了。其实非常的简单,python这门语言给了我们很多高级的特性,以及一些强大的api减少了我们很多的工作。让我们有足够的时间去优化我们的代码以及去如何加强我们的爬虫,使其变的更健壮。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值