网络爬虫: 一种按照一定规则,自动抓取互联网信息的程序或者脚本, 爬虫的本质是模拟浏览器打开网页,获取网页中我们想要的那部分数据
Python为什么适合爬虫:
Python的脚本特性, Python易于配置, 对字符的处理液非常灵活, 加上Python拥有丰富的网络抓取模块.
Python爬虫的组成部分
主要包含五个部分: 调度器, URL管理器, 网页下载器, 网页解析器, 应用程序(爬取的有价值数据)
调度器:相当于一台电脑的CPU,主要负责调度URL管理器,下载器,解析器之间的协调工作。
URL管理器:包括待爬取的URL地址和一爬取的URL地址,防止重复抓取URL和循环抓取URL,实现URL管理器主要用三种方式,通过内存,数据库,缓存数据库来实现。
网页下载器:通过传入一个URL地址来下载,将网页转换成一个字符串,网页下载器有urllib(Python官方内置标准库)包括需要登陆,代理和cookie,requests(第三方包)。
网页解析器:将一个网页字符串进行解析,可以按照我们的要求来提取出我们最有用的信息,也可以根据DOM树的解析方式来解析。网页解析器有正则表达式(只管,将网页转换成字符串通过模糊匹配的方式来提取有价值的信息,当文档比较复杂的时候,该方法提取数据的时候就会非常困难),html.parser(Python自带),beautifulsoup(第三方插件,可以解析xml和HTML),html.parser和beautifulsoup以及lxml都是以DOM树的方式进行解析的。
应用程序:就是从网页中提取的有用数据组成的一个应用。
URI和URL的概念
URL
在1989年,网络发明人蒂姆·比纳斯-李 就提出了网站的三大支柱:
- URL,跟踪Web文档的地址系统
- HTTP,一个传输协议,以便在给定URL时查找文档
- HTML,允许嵌入超链接的文档格式
web的最初目的是提供一种简单的方式来访问,阅读和浏览文本文档。从那时起,网络已经发展到提供图像,视频和二进制数据的访问,但是这些改进几乎没有改变三大支柱。
在web之前,很难访问文档并从一个文档跳转到另一个文档。WWW(World Wide Web)简称3W,使用同一资源定位符(URL)来标志WWW上的各种文档
完整的工作流程:
1) Web用户使用浏览器(指定URL)与web服务器建立连接,并发送浏览请求。
2) Web服务器吧URL转换为文件路径,并放回信息给Web浏览器。
3) 通讯完成,关闭连接。
HTTP:超文本传送协议(HTTP)是在客户程序(如浏览器)与WWW服务器之间进行交互所使用的协议。HTTP使用同一资源标识符(Uniform Resource Identifiers, URI)来传输数据和创建使用,它使用TCP连接进行可靠传输,服务器默认监听在80端口。
URL的组成:
1)协议部分:他表示浏览器必须使用的协议来请求资源(协议是在计算机网络中交换或传输数据的一套方式),通常对于网络,协议是HTTP和HTTPS。
2)域名部分(www.baidu.com):一个URL中,域名部分也可以直接使用IP地址。
3)端口部分(80):域名和端口之间使用 ‘:’ 作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口(默认端口可以省略,默认端口可以修改)。
4)资源路径:资源路径包含,虚拟网络部分和文件名部分
虚拟目录部分( /path/to/ )从域名后第一个“/”到最后一个“/”之间的部分,是虚拟目录部分,虚拟目录也不是一个URL必须的部分
文件名部分(mylike.html)从域名最后一个“/” 开始到“?”为止,是文件名部分
5)参数部分: 从?开始到 # 之间,参数可以有多个,之间的间隔符是&
6)锚部分: #xxx 一种具体的URI,即URL可以用来标识一个资源,并且还指明了如何locate(定位)这个资源
引入模块
模块(module):用来从逻辑上组织Python代码(变量,函数,类),本质就是py文件,提供代码的可维护性,Python使用import来导入模块
#导入内置模块
import sys
#导入标准库
import os
#导入第三方库(需要安装:pip install bs4)
import bs4 #导入整个模块
from bs4 import BeautifulSoup #导入指定模块的部分到当前工作空间
urllib库
1. request模块:HTTP请求模块,可以用来模拟发送请求,只需要传入URL以及额外参数,就可以模拟浏览器访问网页
# 语法
"""
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
"""
"""
参数说明:
url: 请求的url,也可以是request对象
data: 请求的data,post请求会用到
timeout:链接超时时间
cafile和capath:用于 HTTPS 请求中,设置 CA 证书及其路径 // 没搞懂啥意思
cadefault:忽略*cadefault*参数; // 没搞懂啥意思
context:如果指定了*context* 则它必须是一个ssl.SSLContext实例 // 没搞懂啥意思
"""
"""
返回值说明:
urlopen() 返回对象HTTPResponse提供的方法和属性:
read(), readline(),readlines(),fileno(),close() 对HTTPResponse类型数据进行操作;
info() 返回HTTPMessage对象,表示远程服务器 返回的头信息
getcode() 返回HTTP状态码
geturl() 返回请求的url
getheaders() 相应的头部信息
status 返回的状态码
reason 返回状态的详细信息
"""
def url_test():
url = "http://www.baidu.com" # 拿百度测试
response = urllib.request.urlopen(url) # get请求
print(f'response: {response}') # <http.client.HTTPResponse object at 0x0000019484923580>
# 读取响应体
print(f'响应体:{response.read()}') # 调用read方法得到的是bytes对象
print(f'HTTP版本号: {res.version}') #HTTP版本号
print(f'响应码(.getcode()): {res.getcode()}') # 获取响应码
print(f'响应码(.status): {res.status}') # 获取状态 status
print(f'获取响应描述字符串(.reason): {res.reason}')
print(f'获取实际请求的页面URL: {res.geturl()}') # 防止重定向
print(f'获取响应头信息(字符串):\n{res.info()}') # 字符串
print(f'获取响应头信息(数组): {res.getheaders()}') # 数组
print(f'获取特定响应头信息ContentType: {res.getheader(name="Content-Type")}') # 获取特定响应头信息ContentType
# 超时时间测试
def time_out():
url = "http://www.baidu.com"
try:
res = urllib(url,timeout=0.01)
print(res)
except urllib.errorURLError as e:
# error模块
# uillib.error模块为urllib。request所引发的异常定义了异常类,基础异常类是URLError
if hasattr(e, 'code'):
print(f'time_out: {e.code}')
if hasattr(e, 'reason'):
print(f'reason: {e.reason}')
print(f'Time Out: {e}')
# 伪装heaaders
def headers_customer():
url = "http://douban.com"
headers = {
"User_Agent" : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'
}
req = urllib.request.Request(url, headers=headers)
print(req)
print(urllib.request.urlopen(url).read().getcode('utf-8'))
# urllib.request.urlretrieve()
# 直接将远程的数据下载到本地
"""
语法:
urlretrieve(url, filename=None, reporthook=None, data=None)
url: 网址地址
reporthook:回调函数,当连接上服务器,以及相应的数据块传输完毕时会触发该回调,可以利用这个回调函数来显示当前的下载进度
data: 指post到服务器的数据,该方法返回一个包含两个元素的(filename,headers)元组,filename表示保存到本地的路径,header表示服务器的响应头
"""
def down_load()
url = "http://baidu.com"
filename = "D:\\down\\one.index"
urllib.request.urlretrieve(url, fileName, call_back)
def call_back(blocknum, blocksize, totalsize) {
if totalsize == 0:
percent = 0
else:
percent = blocknum * blocksize / totalsize
if percent > 1.0:
percent = 1.0
percent = percent * 100
print("download : %.2f%%" % percent)
}
Beautiful Soup 4库
给项目添加Beautiful Soup库
# 引入Beautiful Soup 4库
from bs4 import BeautifulSoup
BeautifulSoup将一个复杂的HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象归纳为4中,Tag. NavigableString, Beautifulsoup. Comment
Tag对象和HTML原生文档中的tag相同
Tag可能包含多个字符串或其他的Tag,这些都是这个Tag的子节点
Tag的名字,操作文档书最简单的方法就是利用tag的name,如果想要获取<header>标签,只要用soup.head
通过 . 获取属性的方法只能获取到当前名字的第一个Tag, 例如有多个div标签,则只能获取到文档树结构中的第一个div标签
如果想要获取所有的标签,或是比通过名字获取内容更复杂的方法时,就需要用到 搜索文档树 的方法
例: find_all() -> soup.find_all('div') # 查找子节点中所有的div Tag
from bs4 import BeautifulSoup
def bs4_test(text):
soup1 = BeautifulSoup(text, 'lxml')
# BeautifulSoup将复杂的HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象归纳为4种:Tag,NavigableString,BeautifulSoup,Comment
# Tag对象和XML或HTML原生文档中的tag相同
# tag可能包含多个字符串或其他的tag,这些都是这个Tag的子节点。Beautiful Soup 提供了许多查找和操作子节点的方法。
# Beautiful Soup中字符串节点不支持这些属性,因为字符串没有子节点。
# Tag的名字,操作文档书最简单的方法就是利用tag的name,如果想要获取<header>标签,只要用soup.head
# 通过.获取属性的方法只能获取到当前名字的第一个tag, 例如有多个div标签,则只能获取到文档树结构中的第一个div标签
# 如果想要获取所有的标签,或是比通过名字获取内容更复杂的方法时,就需要用到 搜索文档树 的方法
# 例: find_all() -> soup.find_all('div')
# .content / .children
# Tag的 .content 属性可以将tag的全部子节点以列表的方式输出
# body下的contents列表
# print(soup1.body.contents)
# Tag的 .children,展示节点下所有的子节点
num = 0
for chi in soup1.body.children:
num = num + 1
print(f"计数:{num}", chi)
# .descendants 会对节点的子孙节点进行递归循环
num2 = 0
for chi2 in soup1.body.descendants:
num2 = num2 + 1
for chi4 in chi2.stripped_strings: # 去除多余的空格和空行
print(f".stripped_strings方法:{chi4}")
# print(f"计数:{num2}")
# print(chi2)
# print(f"计数:{num2}")
print(f'.get_txt()方法:{soup1.get_text()}')
# 如果节点下面只包含一个 NavigableString 类型的子节点,那么这个tag可以使用.string得到子节点
# 如果节点下面只有一个子节点,那么这个tag也可以使用.string方法,输出结果与当前唯一子节点的.string结果相同
# 如果节点下面包含多个子节点,则输出None
# for chi3 in soup1.find_all('li'):
# for chi4 in chi3.stripped_strings: # 去除多余的空格和空行
# print(f".stripped_strings方法:{chi4}")
# 如果tag中包含多个字符串,可以使用.strings来循环获取
# 如果字符串的空格空行,可以使用.stripped_strings去除多余空白内容
# 父节点:
# 每个tag或字符串都有父节点:包含当前内容的tag
re库
正则表达式
# 正则表达式RE模块 安装方式通bs4模块
import re