python新闻文本爬虫_大规模异步新闻爬虫:实现一个同步定向新闻爬虫

前面,我们先写了一个简单的百度新闻爬虫,可是它槽点满满。接着,我们实现了一些模块,来为我们的爬虫提供基础功能,包括:网络请求、网址池、MySQL封装。

有了这些基础模块,我们的就可以实现一个更通用化的新闻爬虫了。为什么要加“定向”这个修饰词呢?因为我们的爬虫不是漫无目的的广撒网(广撒网给我们带来的服务器、带宽压力是指数级增长的),而是在我们规定的一个范围内进行爬取。

python-news-crawler-sync.jpg

这个范围如何规定呢?我们称之为:hub列表。在实现网址池的到时候,我们简单介绍了hub页面是什么,这里我们再简单定义一下它:hub页面就是含有大量新闻链接、不断更新的网页。我们收集大量不同新闻网站的hub页面组成一个列表,并配置给新闻爬虫,也就是我们给爬虫规定了抓取范围:host跟hub列表里面提到的host一样的新闻我们才抓。这样可以有些控制爬虫只抓我们感兴趣的新闻而不跑偏乱抓一气。

这里要实现的新闻爬虫还有一个定语“同步”,没错,这次实现的是同步机制下的爬虫。后面会有异步爬虫的实现。

同步和异步的思维方式不太一样,同步的逻辑更清晰,所以我们先把同步爬虫搞清楚,后面再实现异步爬虫就相对简单些,同时也可以对比同步和异步两种不同机制下爬虫的抓取效率。

这个爬虫需要用到MySQL数据库,在开始写爬虫之前,我们要简单设计一下数据库的表结构:

1. 数据库设计

创建一个名为crawler的数据库,并创建爬虫需要的两个表:

crawler_hub :此表用于存储hub页面的url

+------------+------------------+------+-----+-------------------+----------------+

| Field | Type | Null | Key | Default | Extra |

+------------+------------------+------+-----+-------------------+----------------+

| id | int(10) unsigned | NO | PRI | NULL | auto_increment |

| url | varchar(64) | NO | UNI | NULL | |

| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |

+------------+------------------+------+-----+-------------------+----------------+

创建该表的语句就是:

CREATE TABLE `crawler_hub` (

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,

`url` varchar(64) NOT NULL,

`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

PRIMARY KEY (`id`),

UNIQUE KEY `url` (`url`)

) ENGINE=MyISAM DEFAULT CHARSET=utf8

对url字段建立唯一索引,可以防止重复插入相同的url。

crawler_html :此表存储html内容

html是大量的文本内容,压缩存储会大大减少磁盘使用量。这里,我们选用lzma压缩算法。表的结构如下:

+------------+---------------------+------+-----+-------------------+----------------+

| Field | Type | Null | Key | Default | Extra |

+------------+---------------------+------+-----+-------------------+----------------+

| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |

| urlhash | bigint(20) unsigned | NO | UNI | NULL | |

| url | varchar(512) | NO | | NULL | |

| html_lzma | longblob | NO | | NULL | |

| created_at | timestamp | YES | | CURRENT_TIMESTAMP | |

+------------+---------------------+------+-----+-------------------+----------------+

创建该表的语句为:

CREATE TABLE `crawler_html` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`urlhash` bigint(20) unsigned NOT NULL COMMENT 'farmhash',

`url` varchar(512) NOT NULL,

`html_lzma` longblob NOT NULL,

`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,

PRIMARY KEY (`id`),

UNIQUE KEY `urlhash` (`urlhash`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

该表中,我们存储了url的64位的farmhash,并对这个urlhash建立了唯一索引。id类型为无符号的bigint,也就是2的64次方,足够放下你能抓取的网页。

farmhash是Google开源的一个hash算法。64位的hash空间有2的64次方那么大,大到随意把url映射为一个64位无符号整数,也不会出现hash碰撞。老猿使用它多年也未发现hash碰撞的问题。

由于上传到pypi时,farmhash这个包名不能用,就以pyfarmhash上传到pypi上了,所以要安装farmhash的python包,应该是:

pip install pyfarmhash

(多谢评论的朋友反馈这个问题。)

数据库建立好后,我们就可以开始写爬虫的代码了。

2. 新闻爬虫的代码实现

#!/usr/bin/env python3

# Author: veelion

import urllib.parse as urlparse

import lzma

import farmhash

import traceback

from ezpymysql import Connection

from urlpool import UrlPool

import functions as fn

import config

class NewsCrawlerSync:

def __init__(self, name):

self.db = Connection(

config.db_host,

config.db_db,

config.db_user,

config.db_password

)

self.logger = fn.init_file_logger(name + '.log')

self.urlpool = UrlPool(name)

self.hub_hosts = None

self.load_hubs()

def load_hubs(self,):

sql = 'select url from crawler_hub'

data = self.db.query(sql)

self.hub_hosts = set()

hubs = []

for d in data:

host = urlparse.urlparse(d['url']).netloc

self.hub_hosts.add(host)

hubs.append(d['url'])

self.urlpool.set_hubs(hubs, 300)

def save_to_db(self, url, html):

urlhash = farmhash.hash64(url)

sql = 'select url from crawler_html where urlhash=%s'

d = self.db.get(sql, urlhash)

if d:

if d['url'] != url:

msg = 'farmhash collision: %s <=> %s' % (url, d['url'])

self.logger.error(msg)

return True

if isinstance(html, str):

html = html.encode('utf8')

html_lzma = lzma.compress(html)

sql = ('insert into crawler_html(urlhash, url, html_lzma) '

'values(%s, %s, %s)')

good = False

try:

self.db.execute(sql, urlhash, url, html_lzma)

good = True

except Exception as e:

if e.args[0] == 1062:

# Duplicate entry

good = True

pass

else:

traceback.print_exc()

raise e

return good

def filter_good(self, urls):

goodlinks = []

for url in urls:

host = urlparse.urlparse(url).netloc

if host in self.hub_hosts:

goodlinks.append(url)

return goodlinks

def process(self, url, ishub):

status, html, redirected_url = fn.downloader(url)

self.urlpool.set_status(url, status)

if redirected_url != url:

self.urlpool.set_status(redirected_url, status)

# 提取hub网页中的链接, 新闻网页中也有“相关新闻”的链接,按需提取

if status != 200:

return

if ishub:

newlinks = fn.extract_links_re(redirected_url, html)

goodlinks = self.filter_good(newlinks)

print("%s/%s, goodlinks/newlinks" % (len(goodlinks), len(newlinks)))

self.urlpool.addmany(goodlinks)

else:

self.save_to_db(redirected_url, html)

def run(self,):

while 1:

urls = self.urlpool.pop(5)

for url, ishub in urls.items():

self.process(url, ishub)

if __name__ == '__main__':

crawler = NewsCrawlerSync('yuanrenxyue')

crawler.run()

3. 新闻爬虫的实现原理

上面代码就是在基础模块的基础上,实现的完整的新闻爬虫的代码。

它的流程大致如下图所示:

news-crawler-flow.jpg

我们把爬虫设计为一个类,类在初始化时,连接数据库,初始化logger,创建网址池,加载hubs并设置到网址池。

爬虫开始运行的入口就是run(),它是一个while循环,设计为永不停息的爬。先从网址池获取一定数量的url,然后对每个url进行处理,

处理url也就是实施抓取任务的是process(),它先通过downloader下载网页,然后在网址池中设置该url的状态。接着,从下载得到的html提取网址,并对得到的网址进行过滤(filter_good()),过滤的原则是,它们的host必须是hubs的host。最后把下载得到的html存储到数据。

运行这个新闻爬虫很简单,生成一个NewsCrawlerSync的对象,然后调用run()即可。当然,在运行之前,要先在config.py里面配置MySQL的用户名和密码,也要在crawler_hub表里面添加几个hub网址才行。

##思考题: 如何收集大量hub列表

比如,我想要抓新浪新闻 news.sina.com.cn , 其首页是一个hub页面,但是,如何通过它获得新浪新闻更多的hub页面呢?小猿们不妨思考一下这个问题,并用代码来实现一下。

这个时候已经抓取到很多网页了,但是怎么抽取网页里的文字呢?

下一篇我们讲:

网页正文抽取

yrx_banner_pic.jpg

我的公众号:猿人学 Python 上会分享更多心得体会,敬请关注。

***版权申明:若没有特殊说明,文章皆是猿人学 yuanrenxue.com 原创,没有猿人学授权,请勿以任何形式转载。***

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值