这个实战例子是构建一个大规模的异步新闻爬虫,但要分几步走,从简单到复杂,按部就班的来构建这个Python爬虫html
本教程全部代码以Python 3.6实现,不兼顾Python 2,强烈建议你们使用Python 3python
要抓取新闻,首先得有新闻源,也就是抓取的目标网站。国内的新闻网站,从中央到地方,从综合到垂直行业,大大小小有几千家新闻网站。百度新闻(news.baidu.com)收录的大约两千多家。那么咱们先从百度新闻入手。正则表达式
打开百度新闻的网站首页:news.baidu.com
咱们能够看到这就是一个新闻聚合网页,里面列举了不少新闻的标题及其原始连接。如图所示:数据库
咱们的目标就是从这里提取那些新闻的连接并下载。流程比较简单:浏览器
根据这个简单流程,咱们先实现下面的简单代码:服务器
#!/usr/bin/env python3 # Author: veelion import re import time import requests import tldextract def save_to_db(url, html): # 保存网页到数据库,咱们暂时用打印相关信息代替 print('%s : %s' % (url, len(html))) def crawl(): # 1\. download baidu news hub_url = 'http://news.baidu.com/' res = requests.get(hub_url) html = res.text # 2\. extract news links ## 2.1 extract all links with 'href' links = re.findall(r'href=[\'"]?(.*?)[\'"\s]', html) print('find links:', len(links)) news_links = [] ## 2.2 filter non-news link for link in links: if not link.startswith('http'): continue tld = tldextract.extract(link) if tld.domain == 'baidu': continue news_links.append(link) print('find news links:', len(news_links)) # 3\. download news and save to database for link in news_links: html = requests.get(link).text save_to_db(link, html) print('works done!') def main(): while 1: crawl() time.sleep(300) if __name__ == '__main__': main()
简单解释一下上面的代码:cookie
1. 使用requests下载百度新闻首页;
2. 先用正则表达式提取a标签的href属性,也就是网页中的连接;而后找出新闻的连接,方法是:假定非百度的外链都是新闻连接;
3. 逐个下载找到的全部新闻连接并保存到数据库;保存到数据库的函数暂时用打印相关信息代替。
4. 每隔300秒重复1-3步,以抓取更新的新闻。网络
以上代码能工做,但也仅仅是能工做,槽点多得也不是一点半点,那就让咱们一块儿边吐槽边完善这个爬虫吧。session
在写爬虫,尤为是网络请求相关的代码,必定要有异常处理。目标服务器是否正常,当时的网络链接是否顺畅(超时)等情况都是爬虫没法控制的,因此在处理网络请求时必需要处理异常。网络请求最好设置timeout,别在某个请求耗费太多时间。timeout 致使的识别,有多是服务器响应不过来,也多是暂时的网络出问题。因此,对于timeout的异常,咱们须要过段时间再尝试。app
服务器返回的状态很重要,这决定着咱们爬虫下一步该怎么作。须要处理的常见状态有:
记录下这次失败的URL,以便后面再试一次。对于timeout的URL,须要后面再次抓取,因此须要记录全部URL的各类状态,包括:
增长了对网络请求的各类处理,这个爬虫就健壮多了,不会动不动就异常退出,给后面运维带来不少的工做量。
下一节咱们讲对上面三个槽点结合代码一一完善。欲知详情,请听下回分解。
本节中咱们用到了Python的几个模块,他们在爬虫中的做用以下:
1. requests模块
它用来作http网络请求,下载URL内容,相比Python自带的urllib.request,requests更加易用。GET,POST信手拈来:
import requests res = requests.get(url, timeout=5, headers=my_headers) res2 = requests.post(url, data=post_data, timeout=5, headers=my_headers)
get()和post()函数有不少参数可选,上面用到了设置timeout,自定义headers,更多参数可参考requests 文档。
requests不管get()仍是post()都会返回一个Response对象,下载到的内容就经过这个对象获取:
经验之谈: res.text判断中文编码时有时候会出错,仍是本身经过cchardet(用C语言实现的chardet)获取更准确。这里,咱们列举一个例子:
In [1]: import requests In [2]: r = requests.get('http://epaper.sxrb.com/') In [3]: r.encoding Out[3]: 'ISO-8859-1' In [4]: import chardet In [5]: chardet.detect(r.content) Out[5]: {'confidence': 0.99, 'encoding': 'utf-8', 'language': ''}
上面是用ipython交互式解释器(强烈推荐ipython,比Python本身的解释器好太多)演示了一下。打开的网址是山西日报数字报,手动查看网页源码其编码是utf8,用chardet判断获得的也是utf8。而requests本身判断的encoding是ISO-8859-1,那么它返回的text的中文也就会是乱码。
requests还有个好用的就是Session,它部分相似浏览器,保存了cookies,在后面须要登陆和与cookies相关的爬虫均可以用它的session来实现。
2. re模块
正则表达式主要是用来提取html中的相关内容,好比本例中的连接提取。更复杂的html内容提取,推荐使用lxml来实现。
3. tldextract模块
这是个第三方模块,须要pip install tldextract
进行安装。它的意思就是Top Level Domain extract,即顶级域名提取。前面咱们讲过URL的结构,news.baidu.com 里面的news.baidu.com叫作host,它是注册域名baidu.com的子域名,而com就是顶级域名TLD。它的结果是这样的:
In [6]: import tldextract In [7]: tldextract.extract('http://news.baidu.com/') Out[7]: ExtractResult(subdomain='news', domain='baidu', suffix='com')
返回结构包含三部分:subdomain, domain, suffix
4. time模块
时间,是咱们在程序中常常用到的概念,好比,在循环中停顿一段时间,获取当前的时间戳等。而time模块就是提供时间相关功能的模块。同时还有另一个模块datetime也是时间相关的,能够根据状况适当选择来用。
记住这几个模块,在从此的写爬虫生涯中将会受益不浅。