本文基于《Python网络爬虫与信息提取》的学习,参考资料源于“Python网络爬虫与信息提取 北京理工大学:嵩天”,视频链接如下:
学习视频 (学习视频的课程排序不太准确,注意先看某节的简介/介绍,再看内容,最后看总结,【可以参考本文目录顺序】) 视频嵩老师使用python自带的IDLE,而本博客作者使用IDE为pycharm,因此在交互式部分的代码会有出入
HTTP,Hypertext Transfer Protocol,超文本传输协议,是一个基于“请求与响应”模式的、无状态的应用层协议,并采用URL(统一资源定位符)作为定位网络资源的标识。
URL格式 > http://host[:port][path]
host:合法的Internet主机域名或IP地址 port:端口号,缺省(默认)端口为80 path:请求资源的路径
HTTP URL的理解: URL是通过HTTP协议存取资源的Internet路径,一个URL对应一个数据资源。 HTTP协议对资源的操作:
方法说明GET请求获取URL位置的资源HEAD请求获取URL位置资源的响应消息报告,即获取该资源的头部信息POST请求向URL位置的资源后附加新的数据PUT请求向URL位置存储一个资源,覆盖原URL位置的资源PATCH请求局部更新URL位置的资源,即改变该处资源的部分内容DELETE请求删除URL位置存储的资源理解PATCH和PUT的区别 假设URL位置有一组数据UserInfo,包括UserID、UserName等20个字段。 需求:用户修改了UserName,其他不变。
采取PATCH,仅向URL提交UserName的局部更新请求 采取PUT,必须将所有20个字段一并提交到URL,未提交字段被删除
PATCH的最主要好处:节省网络宽带
对比我们可以看到,HTTP协议与Requests对资源操作一一对应。 下面就Requests中的部分方法进行操作:
import requests r = requests.head("http://httpbin.org/get") print(r.headers){‘Date’: ‘Fri, 03 Jul 2020 11:09:50 GMT’, ‘Content-Type’: ‘application/json’, ‘Content-Length’: ‘307’, ‘Connection’: ‘keep-alive’, ‘Server’: ‘gunicorn/19.9.0’, ‘Access-Control-Allow-Origin’: ‘*’, ‘Access-Control-Allow-Credentials’: ‘true’}
Requests库的post()方法
import requests payload = { "key1" : "value1", "key2" : "value2" } r = requests.post("http://httpbin.org/post", data=payload) print(r.text)可以看到,使用post()方法向URL提交一个字典时,网页自动编码到form(表单)中
import requests r = requests.post("http://httpbin.org/post", data="ABC") print(r.text)可以看到,使用post()方法向URL提交一个字符串时,网页自动编码到data中 Requests库的post()方法
import requests payload = { "key1" : "value1", "key2" : "value2" } r = requests.put("http://httpbin.org/post", data=payload) print(r.text)向URL发送post请求,相当于传递新增数据,而put还会覆盖原有数据。当然,在一些URL中,POST和PUT方法也会不被允许使用,上述例子在我访问的时候已经不被运行使用了。上述例子主要是为了了解使用这些方法后,URL会如何处理这些数据。
首先使用get方法发出请求
requests.get(yrl, params = None, **kwargs) url:拟获取页面的url链接 params:url中的额外参数,字典或字节流格式,可选 **kwargs:12个控制访问的参数注意Requests库的2个重要对象,我们不仅要发出请求,还要得到响应
下列就是我们调用响应的方法:
属性说明r.status_codeHTTP请求的返回请求状态码r.textHTTP响应页面内容的字符串形式r.encoding从HTTP header中猜猜的响应内容编码方式r.apparent_encoding从内容中分析出的响应内容编码方式(备选编码方式)r.contentHTTP响应内容的二进制形式下面我们就访问b站首页并获取响应内容
import requests r = requests.get("https://www.bilibili.com/") print(r.status_code) print(type(r)) print(r.headers)下面就是一些常见的状态码以及对应的含义: 那么使用request访问基本流程就是这样的: 对于响应内容的编码类型,有两种获取方式,r.encoding是根据HTTP头文件中是否存在charset来判断,而r.apparent_encoding是就返回内容分析,因此从后者中可以更准确了解响应内容的编码格式。
r.encoding : 如果header中不存在charset,则认为编码为ISO-8859-1 r.apparent_encoding : 根据网页内容分析出的编码方式
import requests r = requests.get("https://www.baidu.com/") print(r.encoding) print(r.apparent_encoding)ISO-8859-1 utf-8 这样就可以便于我们去解析响应内容,如果直接返回原格式相应内容:
下面我们修改解析方式
import requests r = requests.get("https://www.baidu.com/") r.encoding = "utf-8" print(r.text)这样可以解析中文,使得人眼可读性提高
requests.request(method, url, **kwargs) method : 请求方式,对应get / put / post / put / patch / delete / options 7种 url : 拟获取页面的url链接 **kwargs : 控制访问参数,共13个
下面介绍 13个访问的控制参数:
参数用途params字典或字节序列,作为参数增加到url中data字典、字节序列或文件对象,作为Request的内容jsonJson格式的数据,作为Request的内容headers字典,HTTP定制头cookies字典或者CookieJar,Request中的cookieauth元组,支持HTTP认证功能files字典类型,传输文件timeout设定超时时间,单位为秒proxies字典类型,设定访问代理服务器,可以增加登录认证allow_redirectsTrue/False,默认为True,重定向开关streamTrue/False,默认为True,获取内容立即下载开关verifyTrue/False,默认为True,认证SSL证书开关cert本地SSL证书路径SSL 证书就是遵守 SSL协议,由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。
下面介绍requests的其他方法: 其中,url : 拟获取页面的url链接, **kwargs : 13个访问参数 在下列这些方法中,有些参数是必须指定的,那么13个参数中剩余的参数就作为自定义参数
requests.head(url, **kwargs) requests.post(url, data = None, json = None, **kwargs) requests.put(url, data = None, **kwargs) requests.patch(url, data = None, **kwargs) requests.delete(url, **kwarg)对于requests库中所有方法,最常用的就是request、get和head三个方法。在我们访问时,可以通过请求传递信息,这些信息就可能导致网络安全性问题,因此上面其他的几个方法很有可能就不被允许使用。
以上介绍了requests库的方法,在使用时最多的就是request、get和head。同时,我们还需要认识到——“网络链接有风险,异常处理很重要!”下面就介绍如何处理异常: 首先了解一下requests的异常:
异常说明requests.ConnectionError网络连接错误异常,如DNS查询失败、拒绝连接等requests.HTTPErrorHTTP错误异常requests.URLRequiredURL缺失异常requests.TooManyRedirects超过最大重定向次数,产生重定向异常requests.ConnectTimeout连接远程服务器超时异常requests.Timeout请求URL超时,产生超时异常r.raise_for_status()如果不是200,产生异常requests.HTTPError import requests try: #尝试去运行 r = requests.get("https://www.baidu.com/") r.raise_for_status() #判断响应状态码是否为200,如果不是200,它就会产生一个HttpError的异常 r.encoding = "utf-8" print(r.text) except: print("产生异常")由此我们获得一个通用框架:
import requests def getHTMLText(url): try: r = requests.get(url, timeout = 30) r.raise_for_status()#如果状态码不是200,触发HTTPError异常 r.encoding = "utf-8" return r.text except: return "产生异常" if __name__=="__main__": url = "https://www.bilibili.com/" print(getHTMLText(url))Robots Exclusion Standard网络爬虫排除标准 作用:网站告知网络爬虫哪些页面可以访问,哪些不行 形式:网站根目录下的robots.txt文件
如何找到robots.txt文件:直接在网址后面加上“/robots.txxt”
在上面的robots文件中,作出以下规定: Allow规定以其后开头的URL是允许robot访问的,Disallow规定以其后开头的URL是不允许访问的,这里的User-agent(用户代理)指明了爬虫的引擎。在协议的后面,例如最后一个,表明了:禁止该搜索引擎访问网站的任何部分。 当然,并不是所有网站都会有robots协议,如果没有这个协议,默认是允许所有用户访问任意部分。
无论我们爬取什么数据,网络爬虫都应该遵守robots协议。
网络爬虫会带来很多问题,主要是下面三类: 1、网络爬虫的骚扰 受限于编写水平和目的,网络爬虫将会给web服务器带来巨大的资源开销 2、网络爬虫的法律风险 服务器上数据有产权归属 网络爬虫获取数据后牟利将带来法律风险 3、网络爬虫泄露隐私 网络爬行可能具备突破简单访问控制的能力,获取被保护数据从而泄露个人隐私 因此网页维护者会采取来源审查和发布公告两种方式来限制网络爬虫。
1、来源审查:判断User-Agent进行判断 2、发布公告:Robots协议 除了Robots协议之外,我们在使用爬虫时还需要进行自我约束,过于快速或者频密的网络爬虫都会对服务器产生巨大的压力,可能引起网站封锁你的访问ID,甚至采取进一步的法律行动。因此,在使用爬虫时,需要对请求速度进行合适的调整。
浏览商品信息
import requests r = requests.get("https://item.jd.com/12441345483.html") print(r.status_code) print(r.encoding)利用get方法获取网络链接状态码和网页编码类型
200 UTF-8
然后使用r.text打印内容,当然,这里需要登陆,这就需要提交一组账户密码组织键值对对象给网页,或者事先登录(这里就自己动手吧)。
这是课程中出现的问题,亚马逊网站直接拒绝我们的请求,因为我们发出的请求头很坦诚的告诉亚马逊网站自己是python发出的,因此我们需要对请求进行处理。(当然,由于时差影响,我访问的时候并没有被拒绝) 下面介绍定制请求头,
import requests b = { "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" \ " Chrome/85.0.4170.0 Safari/537.36 Edg/85.0.552.1" } url = "https://www.amazon.cn/dp/B013JV3G2K/ref=sr_1_1?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E"\ "7%BD%91%E7%AB%99&crid=3DYCPY7I9J9S7&keywords=%E9%B2%81%E8%BF%85%E5%85%A8%E9%9B%86&qid=1"\ "593834844&sprefix=%E9%B2%81%E8%BF%85%2Caps%2C173&sr=8-1" r = requests.get(url,headers = b) print(r.text)这个user-agent是指用户代理,下面我们介绍如何定制请求头:
请求头Headers提供了关于请求、响应或其他发送实体的信息。对于爬虫而言,请求头十分重要,尽管上述示例没有指定请求头。如果没有指定请求头或者请求的请求头和实际网页不一致,就可能无法返回正确是结果。 requests并不会基于定制的请求头Headers的具体情况改变自己的行为,只是在最后的请求中,所有的请求头信息都会被传递进去。 下面介绍如何找到Headers: 1、使用浏览器的检查(开发者模式) 2、打开网络(Network),刷新页面,找到需要请求的页面 3、单击请求的页面(如果只是找用户代理,可任意选取一个) 4、在标头下面找到“请求标头”,就可以看到用户代理
百度关键词接口:
http://www.baidu.com/s?wd= keyword import requests a = {"wd":"Python"}#创建一个键值对用作提交对象 r = requests.get("http://www.baidu.com/s", params= a) print(r.status_code) print(r.request.url) print(len(r.text))200 http://www.baidu.com/s?wd=Python 349937
查看字符串长度我们就可以看到响应的内容非常多,就不必将其打印。类似的,这里直接给出360搜索实例
网络图片链接格式:http://www.example.com/picture.jpg
path:存储路径
原理:利用URL接口,向网页提交一个IP地址
http://m.ip138.com/ip.asp?ip= ipaddressBeautiful Soup库,也叫beautifulsoup4或bs4
注意Beautiful Soup中大小写
Beautiful Soup类就是bs4中的一个类
上面就是使用HTML解析器
这些将在接下来的内容将。
下面看一下HTML解析样式:
import requests from bs4 import BeautifulSoup r = requests.get("https://www.bilibili.com/") soup = BeautifulSoup(r.text,"html.parser") b = soup.prettify() print(b)HTML的基本格式就是成对的标签
要想达到遍历,利用循环即可
需要注意,平行遍历发生在同一父节点的各节点间,同时,我们不能直接认为平行遍历返回的下一个节点就是标签类型。
注意,如果遍历中使用的方法需要迭代,那么就只能用for循环来完成遍历。
实例:
XML就是由HTML发展来的通用信息标记形式
注意,组织信息时,键值对都需要双引号包裹,只有值为数值可以不用。
以最好大学网为例:
http://www.zuihaodaxue.com/zuihaodaxuepaiming2020.html
首先查看该网页的robots协议,返现该网页不存在,也就是无限制: 接下来我们简要分析定向的可行性,找到我们想要获取的内容: 1、使用浏览器的检查 2、查看网络,使用搜索,输入关键字“清华”,找到其所在位置 我们可以看到旁边的行数,确定其在源代码中的位置,接下来找到其在源代码中所处的位置: 可以看到其在中的中的中
在源代码页面可以使用快捷键ctrl+f,可以调出搜索框,也可以通过这种方式进行查到位置
我们可以看到这些内容是二维数据,可以用列表进行存储
下面就是输出的结果:
关于.format的用法 . 我们可以看到上面结果,中文并不是完全居中对齐,当中文不够,系统会用西文字符填充,而中西文的空格占位不同,这就会导致中文对齐问题
解决方法: 采用中文字符的空格填充chr(12288)
def printUnivList(ulist, num): tplt = "{0:^6}\t{1:^10}\t{2:^6}" #制定输出规则 print(tplt.format("排名","学校名称","总分",chr(12288)))#替换 for i in range(num): u = ulist[i] print(tplt.format(u[0], u[1], u[2]))
由上述例子,我们可以看出正则表达式可以将一段复杂的内容简洁地表达,除此之外,正则表达式还具有以下功能:
正则表达式常用在字符串匹配,在使用正则表达式时,我们需要进行编译,
注意:{ }的扩展只针对其前的一个字符
这里的第一个实例,其效果和去掉 ? 一样,这里涉及最小匹配,将在后面解释
import re a = "pyyn" m = re.search(r"p(y|yt|yth|ytho)n",a) n = re.search(r"p(y|yt|yth|ytho)?n",a) print(m,"\n",n)返回结果都是None
原生字符串就是在字符串前面加一个“r”,注意,原生字符串中不包含转义字符,因此在使用正则表达式需要用到转义字符时,最好使用原生字符串。
下面就一一解释:
import re a = "pyn" m = re.search(r"p(y|yt|yth|ytho)n",a) print(m) print(m.group())<re.Match object; span=(0, 3), match=‘pyn’> pyn
可以看到,这里调用.group直接输出匹配结果,但是要注意一点,如果没有匹配到结果,那么就会返回空值,此时用group就会返回异常,因此在使用时,为了避免错误,就需要处理异常。
这个函数是将匹配到的结果去掉,返回一个列表,可使用maxsplit参数进行分割
import re m = re.split(r"[1-9]\d{4}","cmf10086 dli10084") n = re.split(r"[1-9]\d{4}","cmf10086 dli10084",maxsplit=1) print(m) print(n)[‘cmf’, ’ dli’, ‘’] [‘cmf’, ’ dli10084’]
注意,这个函数在引用时需要用for循环操作
import re for m in re.finditer(r"[1-9]\d{4}","cmf10086 dli10084"): if m: print(m.group(0))10086 10084
我们还可以将对象单独存储,这样我们就可以重复调用已知对象并重复操作
我们还可以将正则表达式编译成正则表达式对象,这样就可以重复调用这样正则表达式,那么在使用时,就不需要再规定pattern和flags这两个参数了。
Scrapy爬虫框架有7个模块 其中,engine、downloader、scheduler(调度器)是已有的,用户需要编写spiders、item pipelines(配置)模块。
Engine>>> 控制所有模块之间的数据流 根据条件触发事件 Downloader>>> 根据请求下载网页 Scheduler>>> 对所有爬虫请求进行调度
这三者是不需要用户进行修改,但是这三者间有一个中间键,用于用户可控制的配置:
Spider>>> 解析Downloader返回的响应(Reponse) 产生爬取项(scraped item) 产生额外的爬取请求(Request) Item Pipelines>>> 以流水线方式处理Spider产生的爬取项 由一组操作顺序组成,类似流水线,每个操作是一个Item Pipelines类型 可能操作包括:清理、检验和查重爬取项中的HTML数据、将数据存储到数据库
这两个模块需要用户进行编写,另外,这这两者之间还有一个中间键:
request类主要的属性或方法: response类主要的属性和方法:
Scrapy爬虫支持多种HTML提取信息的方法:
Beatuiful Soup lxml re Xpath Selector CSS Selector
输出小于n的数的平方数
def gen(n): for i in range(n): yield i**2 for i in gen(5): print(i) for i in gen(6): print(i," ",end="")0 1 4 9 16 0 1 4 9 16 25
另外,我们还可以找到取值范围,定义一个列表返回这些数值,然后直接使用平方函数求值即可。
既然有可以直接求,为什么还需要生成器?
生成器相比于一次性列出所有可能的优势 1、节省存储空间 2、响应更迅速 3、使用更灵活
当上述实例取n=1M,那么我们还需要定义一个包含0-1M的列表。在爬虫使用时,我们可能需要访问很多内容时,我们就可以使用生成器,
演示网址
http://python123.io/ws/demo.html 文件名:demo.html
博主使用的是pycharm,下面就介绍,使用pycharm的Terminal终端创建一个Scrapy项目
先试试第二行直接创建,如果没有成功,再执行第一行步骤 ( File -> Setting -> Tools -> Shell path -> 将其修改成本机的cmd位置) Terminal >>> scrapy startproject ____【name】
Terminal终端的位置如图所示:(pycharm左下角) 看到如下信息即为创建成功:
New Scrapy project 'test_scrapy', using template directory 'e:\\Anaconda3\\lib\\site-packages\\scrapy\\templates\\project', created in: G:\PycharmProjects\scrapy\test_scrapy You can start your first spider with: cd test_scrapy scrapy genspider example example.com工程目录
依然是在Terminal终端完成
>cd getdemo #在当前工程下执行你的工程(getdemo即为我的工程名) >getdemo>scrapy genspider demo python123.io #给定名称和爬取网址结果如下:
Created spider ‘demo’ using template ‘basic’ in module: getdemo.spiders.demo
在getdemo的工程下生成了一个爬虫,名为demo 当然,我们还可以手动生成,下面我们看一下这个爬虫的内容
import scrapy class DemoSpider(scrapy.Spider): #scrapy.Spider的子类 name = 'demo' allowed_domains = ['python123.io'] #指定爬虫爬取的网址,只能爬取这个域名以下的链接 start_urls = ['http://python123.io/'] #需要爬取页面的初始内容 def parse(self, response): #解析页面的空方法,parse()用于处理响应,解析内容形成字典,发现新的url爬取请求 pass2020-07-06 22:47:48 [scrapy.core.engine] INFO: Spider closed (finished)
最后就可以在看到getdemo的子目录中看到这个html文件
可以看到,完整版本的爬虫文件中使用了生成器,当需要访问的url很多时,这是就需要考虑使用生成器了。
配置 Pipelines.py文件 通过配置文件,让框架找到我们新定义的类,并且用这个类处理Item提出的相关信息; 同时,我们话可以继续定义对爬取项(Scraped Item)的处理类以及其他函数,完善其功能; 最后,我们找到ITEM_PIPELINES,并将我们定义类的注释效果去掉。
python开发工具可分为两类,如下: python自带的IDLE包括交互式和文本式两种编译器