Python爬虫从入门到精通(1): 爬虫原理, urllib库介绍及5个适合新手练手的爬虫
相信很多人学习python都是先从编写网络爬虫(spider)开始的。网上的python爬虫教程也非常多,小编我也是边看边练而且获益不少,但总觉这些文章有些零散。小编我计划将它们按从易到难的顺序做个比较系统化的技术总结,发布在本公众号里。一方面可以作为自己将来web开发项目的参考,另一方面可以帮助更多人学习掌握这门技术。本文会介绍爬虫的工作原理,python自带的urllib库,并编写5个适合新手练手的爬虫。
什么是爬虫(spider)
爬虫(spider)的本质是一个向网站或URL发送请求, 获取资源后分析并提取有用数据的应用程序。它可以用来获取文本数据,也可以用来下载图片或音乐,还可以用来抢票。各大IT公司如阿里, 百度, 新浪和今日头条都大规模的应用了爬虫技术。比如阿里云网站上的IT技术类文章都是从****, CNBlogs和微信公众号等原创平台上爬来的。新浪上的政府新闻很多也是直接从各大部委网站直接爬过来的。
爬虫的工作程序
小编我把一个爬虫的工作程序分为三步。
-
请求发送: 确定需要爬取数据的目标url以及发送请求(request)时所需要携带的数据和各种HTTP头部信息 (如user-agent, proxy IP, cookie)。发送请求时我们最常用的有python 3自带的urllib库和需要安装的第三分包requests库。
-
数据解析: 对返回的数据(response)进行解析,提取我们所需要的信息。常用的数据解析的库有python自带的html.parser, beautifulsoup(第三方库)、lxml(第三方库)。
-
数据存储: 对第2步提取的数据我们有时候需要对其进行清洗,有时会直接存入数据库,写入磁盘文件(如csv文件)或缓存。
一个最简单的python 3爬虫
我们现在来用python 3自带的urllib库开发一个最简单的爬虫。(注: python 3的urllib库整合了python 2的urllib和urllib2)。我们新建一个spider项目文件夹,再创建一个新文件****_spider.py,添加如下代码。然后再终端输入python ****_spider.py,你就会看见从****首页的html代码了。
# -*- coding: UTF-8 -*- from urllib import request if __name__ == "__main__": response = request.urlopen("https://www.****.net/") html = response.read() print(html)
上面这段代码是这样工作的。
-
通过request的urlopen发送请求到https://www.****.net/,返回的数据存在response变量里。HTTP的工作原理就是用户发送一个请求(request)到某个网址,网站服务器一定会给用户返回相应数据(response)。
-
读取response里的数据存在html变量里,并打印html.
如果你运行上面这个spider,你会发现打印的html里的中文字符都是乱码形式。这是因为python在解析网页时默认用Unicode去解析,而大多数网站是utf-8格式的。这是我们需要先使用html.decode("utf-8”)对返回的数据进行解码(decode),才能显示正确的字符串。
# -*- coding: UTF-8 -*- from urllib import request if __name__ == "__main__": response = request.urlopen("https://www.****.net/") html = response.read() html = html.decode("utf-8") print(html)
使用chardet自动获取网页编码
上例中我们已知****网站编码格式是utf-8的才能对其正确解码。如果不知道目标网页的编码格式我们该怎么办呢? 我们可以通过第三方库chardet自动获取目标网页的编码。常见的网页编码有GB2312, GBK, utf-8和unicode。使用方式如下:
'''自动获取网页编码,需要先pip安装chardet''' # -*- coding: UTF-8 -*- from urllib import request import chardet if __name__ == "__main__": response = request.urlopen("https://www.****.net/") html = response.read() charset = chardet.detect(html) print(charset)
python 3的urllib库介绍
urllib是学习python爬虫需要掌握的最基本的库,它主要包含四个模块:
-
urllib.request基本的HTTP请求模块。可以模拟浏览器向目标服务器发送请求。
-
urllib.error 异常处理模块。如果出现错误,可以捕捉异常。
-
urllib.parse 工具模块。提供URL处理方法, 比如对URL进行编码和解码。
-
urllib.robotpaser 用来判断哪些网站可以爬,哪些网站不可以爬。
在之前案例中,我们使用了urlopen方法打开了一个实际链接url,实际上该方法还能打开一个Request对象, 比如下例中的request_url。两者看似没有什么区别,但实际开发爬虫过程中,我们一般先构建request对象,再通过urlopen方法发送请求。这是因为构建的request url可以包含GET参数或POST数据以及头部信息,这些信息是普通的url不能包含的。
'''urlopen还可以打开request对象''' # -*- coding: UTF-8 -*- from urllib import request if __name__ == "__main__": request_url = request.Request("https://www.****.com/") response = request.urlopen(request_url) html = response.read() html = html.decode("utf-8") print(html)
使用urllib发送带参数的get请求
先构建request对象再使用urlopen方法发送请求的好处是我们可以在request对象里添加更多数据和头部信息。比如下例中我们向****网站搜索页面模拟发送带参数get请求,请求参数是q=大江狗,返回数据是搜索结果。我们先使用urllib的parse.urlencode方法对需要发送的get参数进行url编码, 然后再对url进行拼接。在本例中我们还添加了请求头信息(header)。
import urllib.request import urllib.parse if __name__ == "__main__": url = 'https://so.****.net/so/search/s.do' params = {'q': '大江狗', } header = { "User-Agent": " Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11", } # 使用parse方法对参数进行URL编码 encoded_params = urllib.parse.urlencode(params) # 拼装后的request地址是https://so.****.net/so/search/s.do?q=大江狗 request_url = urllib.request.Request(url + '?' + encoded_params, headers=header) response = urllib.request.urlopen(request_url) print(response.read().decode('utf-8'))
为什么要添加请求头headers? 因为很多网站服务器有反爬机制,一般不带请求头的访问都会被认为是爬虫,从而禁止它们的访问。
使用urllib发送post数据
我们还经常需要模拟向一个网站提交表单数据,这时我们也可以通过先构建request对象加入POST数据,然后再使用urlopen方法发送请求。标准代码如下所示。与发送get请求不同的是此时的url无需要拼接,需要发送的参数也不会出现在url里。
import urllib.request import urllib.parse if __name__ == "__main__": url = 'some_url' post_data = {'name': '大江狗', } header = { "User-Agent": " Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11", } # 使用parse方法对参数进行URL编码 encoded_data = urllib.parse.urlencode(post_data).encode('utf-8') # 使用urllib发送POST数据无需拼接URL request_url = urllib.request.Request(url, headers=header, data=encoded_data) response = urllib.request.urlopen(request_url) print(response.read().decode('utf-8'))
我们现在来看一个使用urlib发送post数据具体的例子,爬取有道翻译上的英文翻译。有时候我们想要爬取的url不一定是显性的。比如下面页面采用AJAX技术加载,当我们刚输完"漂亮的"时候, 翻译结果就已经出来了。我们真正要爬取的url是处理ajax请求的具体url, 而不是fanyi.youdao.com.
如果你用Chrome浏览器,点击右键“检查", 再点击"network", 你就会发现真正处理翻译并返回翻译结果的url是http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule。你还会了解发送请求的方式是POST,返回的相应数据是JSON格式。如果请求方式是POST,我们一定还要了解POST什么数据,服务器才会返回正确的响应(Response)。
如果你继续下拉Headers页面,你就会发现需要POST的数据Form Data里不仅包含了我们需要翻译的词(i), 还包括其它加密用的salt和签名字符串sign。我们在请求里必需把这些data加进去,有道才会返回翻译结果。这就是明显的反爬机制啊。当然高人无处不在,弄清了salt和sign的生成原理,就可以轻易**有道的反爬机制了。
下面是爬取有道翻译结果的完整代码,已经经过测试。感谢无敌网友!
# -*- coding: UTF-8 -*- from urllib import request from urllib import parse import time import random import hashlib import json '''使用urlopen(request_url,data)发送参数''' if __name__ == "__main__": # 对应上图的Request URL url = 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule' header = { "Accept": " application/json, text/javascript, */*; q=0.01", "X-Requested-With": " XMLHttpRequest", "User-Agent": " Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11", "Content-Type": " application/x-www-form-urlencoded; charset=UTF-8", "Accept-Language": " zh-CN,zh;q=0.9" } i =input("Translation for: ") u = 'fanyideskweb' d = i f = str(int(time.time() * 1000) + random.randint(1, 10)) c = 'ebSeFb%=XZ%T[KZ)c(sy!' md5 = hashlib.md5() md5.update(u.encode('utf-8')) md5.update(d.encode('utf-8')) md5.update(f.encode('utf-8')) md5.update(c.encode('utf-8')) sign = md5.hexdigest() data = { "i": i, "from": "AUTO", "to": "AUTO", "smartresult": "dict", "client": "fanyideskweb", "salt": f, "sign": sign, "doctype": "json", "version": "2.1", "keyfrom": "fanyi.web", "action": "FY_BY_REALTIME", "typoResult": "false" } # 使用urlencode方法转换标准格式 data = parse.urlencode(data).encode('utf-8') # 传递Request对象和转换完格式的数据 request_url = request.Request(url, data=data, headers=header) response = request.urlopen(request_url) # 如果200,获取response成功 print(response.getcode()) # 读取信息并解码, 转化为json格式 json_result = json.loads(response) translation_result = json_result['translateResult'][0][0]['tgt'] # 打印翻译信息 print("翻译的结果是:%s" % translation_result)
爬取效果如下:
异常的处理
我们的爬虫在实际运行过程中一定会碰到各种各样的异常, 比如一个坏链接或者没有相应权限。我们必需学会如何捕捉那些异常和处理那些异常。urllib.error 的模块包括两种主要错误URLError和HTTPError。相应用法如下所示:
import urllib.request import urllib.error try: urllib.request.urlopen('htt://www.baidu.com') except urllib.error.URLError as e: print(e.reason) try: urllib.request.urlopen('https://www.baidu.com/admin/') except urllib.error.HTTPError as e1: print(e1.reason) print(e1.code)
本文的最后一个爬虫-显示本机IP
whatismyip.com是一个可以显示一个用户实际ip地址的网站。我们新建一个叫ip_spider.py的文件, 发送请求到whatismyip.com, 获取返回数据,采用正则匹配ip地址,然后打印出来。下面这段代码你看懂了吗?
# -*- coding: UTF-8 -*- from urllib import request import re if __name__ == "__main__": # 访问网址获取IP url = 'https://www.whatismyip.com/ip-address-lookup/' header = { "User-Agent": " Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11", } # 构建request对象 request_url = request.Request(url, headers=header) # 发送请求获取返回数据 response = request.urlopen(request_url) # 读取相应信息并解码,并利用正则提取IP html = response.read().decode("utf-8") pattern = re.compile(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b') ip_list = re.findall(pattern, html) print(ip_list[0])
小结
我们介绍了爬虫的基本原理,并介绍了python 3自带的urllib库,并用它开发了几个非常简单的python爬虫。在实际开发python爬虫过程中,我们urllib库用得不多,而是使用更强大的requests库,我们后面会专题介绍。另外我们还没有介绍数据的解析和提取,后面再慢慢写了。欢迎关注我的微信公众号【Python与Django大咖之路】
大江狗
2018.9.24