Python爬虫从入门到精通(1): 爬虫原理, urllib库介绍及5个适合新手练手的爬虫

版权声明:本文系大江狗原创,请勿直接copy应用于你的出版物或任何公众平台。 https://blog.csdn.net/weixin_42134789/article/details/82826857

相信很多人学习python都是先从编写网络爬虫(spider)开始的。网上的python爬虫教程也非常多,小编我也是边看边练而且获益不少,但总觉这些文章有些零散。小编我计划将它们按从易到难的顺序做个比较系统化的技术总结,发布在本公众号里。一方面可以作为自己将来web开发项目的参考,另一方面可以帮助更多人学习掌握这门技术。本文会介绍爬虫的工作原理,python自带的urllib库,并编写5个适合新手练手的爬虫。

什么是爬虫(spider)

爬虫(spider)的本质是一个向网站或URL发送请求, 获取资源后分析并提取有用数据的应用程序。它可以用来获取文本数据,也可以用来下载图片或音乐,还可以用来抢票。各大IT公司如阿里, 百度, 新浪和今日头条都大规模的应用了爬虫技术。比如阿里云网站上的IT技术类文章都是从CSDN, 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项目文件夹,再创建一个新文件csdn_spider.py,添加如下代码。然后再终端输入python csdn_spider.py,你就会看见从csdn首页的html代码了。

# -*- coding: UTF-8 -*-

from urllib import request

if __name__ == "__main__":
    response = request.urlopen("https://www.csdn.net/")
    html = response.read()
    print(html)

上面这段代码是这样工作的。

  • 通过request的urlopen发送请求到https://www.csdn.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.csdn.net/")
    html = response.read()
    html = html.decode("utf-8")
    print(html)

使用chardet自动获取网页编码

上例中我们已知csdn网站编码格式是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.csdn.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.csdn.com/")
    response = request.urlopen(request_url)
    html = response.read()
    html = html.decode("utf-8")
    print(html)

使用urllib发送带参数的get请求

先构建request对象再使用urlopen方法发送请求的好处是我们可以在request对象里添加更多数据和头部信息。比如下例中我们向csdn网站搜索页面模拟发送带参数get请求,请求参数是q=大江狗,返回数据是搜索结果。我们先使用urllib的parse.urlencode方法对需要发送的get参数进行url编码, 然后再对url进行拼接。在本例中我们还添加了请求头信息(header)。

import urllib.request
import urllib.parse


if __name__ == "__main__":

    url = 'https://so.csdn.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.csdn.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

猜你喜欢

转载自blog.csdn.net/weixin_42134789/article/details/82826857