Chapter 1 爬虫基础
1.1HTTP基本原理
1 URI和URL
Uniform Resource Identifier URI,统一资源标志符
Universal Resource Locator URL,统一资源定位符
URL是URI的子集
URI的另一个子集为URN,Universal Resource Name,为资源命名但不定位资源
https://github.com/favicon.ico
https访问协议、访问路径(根目录)和资源名称favicon.ico
.ico图标文件
2.HTTP和HTTPS
超文本(Hypertext):网页源代码HTML,浏览器解析得到网页
超文本传输协议(HTTP):保证高效而准确地传送超文本文档,目前广泛使用HTTP1.1版本。ftp,sftp,smb均为协议类型。
HTTPS:在HTTP加入SSL(Secure Socket Layer)层加密。
作用:
- 建立信息安全通道,保证数据传输安全。
- 确认网站真实性,凡使用HTTPS的网站,可点击浏览器地址栏的锁头标志查看网站认证的真实信息,可通过CA机构颁发的安全签章查询。
3.HTTP请求过程
Request请求,;Response响应。
Network面板:
- Type列:请求的文档类型,document表示HTML文档。
- Initiator:请求源。
- Waterfall:网络请求的可视化瀑布流。
4.请求
请求包含四部分:请求方法、请求网址、请求头、请求体。
-
请求方法:GET、POST。
浏览器直接输入URL回车,发起GET请求。数据显示于URL,最多1024字节。
POST:点击“登录”,发起POST请求,数据以表单的形式传输,而不体现在URL。数据包含在请求体,隐示。没有大小限制。 -
请求头包含浏览器标识、cookie、Host。
Host:指定请求资源的主机IP和端口号,其内容为请求URL的原始服务器或网关的位置。从HTTP1.1版本起,请求必须包含此内容。
Cookies:标识对应服务器的会话。在请求头加上Cookies,将其发送给服务器识别出登录状态,返回登录权限可见的内容。
Content-Type:互联网媒体类型
-
请求体承载POST请求的表单数据,GET请求为空。
5.响应
-
响应状态码
-
响应头
Expires:指定响应的过期时间,使代理服务器或浏览器将加载的内容更新到缓存。 -
响应体
请求网页,响应体为HTML代码;请求图片,响应体为二进制图片。
1.2 Web网页基础
1.网页的组成
HTML(骨架)、CSS(皮肤)和JavaScript(血肉)
- HTML,布局标签div嵌套
- CSS(Cascading Style Sheets):层叠样式表
-层叠:样式冲突时依据层叠顺序处理。样式:网页文字大小等格式。
HTML用link标签引入写好的.css文件。
- Javascript,脚本语言,实时动态交互,使用户与信息之间不仅是浏览与显示。扩展名.js
2.网页的结构
- head:定义页面的配置和引用。包含:
title:网页的标题,出现在网页选项卡而非正文中。 - body:正文显示内容。包含:
div:网页的区块。
h2:二级标题。
p:段落。
3.节点树及节点间的关系
标签定义的内容都是节点。DOM(Document Object Model)文档对象模型。中立于平台与语言的接口,允许程序和脚本动态访问和更新文档的内容、结构和样式。
- HTML文档中的所有内容都是节点:
- 整个文档是一个文档节点;
- 每个HTML元素是元素节点; HTML元素内的文本是文本节点;
- 每个HTML属性是属性节点
- 注释是注释节点
4.选择器
<div id="container">
表示为#container,#表示选择id,其后为id名称。
.wrapper,.表示选择class,其后为class名称。
标签名,h2
#container .wrapper p 先选择id为container的节点,再选中内部的class为wrapper的节点,最后再选中其内部的p节点。
div#container .wrapper p.text
不加空格为并列关系。
1.3 爬虫的基本原理
1.爬虫概述
- 获取网页。构造请求发送给服务器,接收到响应并解析。
- 提取信息。正则表达式。beautiful Soup、pyquery、lxml,分别根据网页节点属性、CSS选择器和XPath提取网页信息。
- 保存数据。保存为TXT、JSON文本;或数据库MySQL、MongoDB或远程服务器SFTP。
2.能爬怎样的数据
页面返回HTML代码,或API接口返回JSON字符串。
3.JavaScript渲染的页面
原始的HTML代码为空壳。分析后台Ajax接口,或使用Selenium、Splash等库模拟JavaScript的渲染。
1.4 Session和Cookie
1. 静态网页。动态网页
动态解析URL的变化,实现登录和注册。
2.无状态HTTP
HTTP协议对事务处理没有记忆能力。
浏览器端:Cookies。服务端:Session(会话)。
会话Cookie和持久Cookie
5.常见误区
关闭浏览器不会导致Session被删,服务器为Session设置一个失效时间。
1.6 多线程和多进程的基本原理
1.多线程
进程:可以独立运行的程序单位。进程是线程的集合。
线程:操作系统进行运算调度的最小单位,是进程的最小运行单元。
2.并发(concurrency)和并行(parallel)
并发:同一时刻仅一条指令执行,但多个线程的对应指令被快速轮换执行。
并行:同一时刻多条指令在多个处理器同时执行。
3.多线程适用场景
一个程序进程中,某些操作需要等待,如数据库查询结果的返回、网页结果的响应。IO密集型任务——爬虫。
CPU密集型任务,开启多线程始终忙于计算。
threading模块实现多线程。
def target(second):
print(f'{threading.current_thread().name} is running')
print(f'{threading.current_thread().name} sleep {second}s')
time.sleep(second)
print(f'{threading.current_thread().name}is ended')
for i in [1,5]:
t=threading.Thread(target=target,args=[i])
t.start()
#t.join()%等待该子线程t运行结束才执行接下来的语句。
print(f'{threading.current_thread().name}is ended')#主线程
结果:
主线程退出先于子线程退出。
join([time]):意味着等到队列为空,再执行别的操作。可选项为超时发生。
setDaemon:守护线程(后台线程)。表示该线程是不重要的,进程退出时不需要等待这个线程执行完成。这样做的意义在于:避免子线程无限死循环,导致退不出程序。
若调用join方法,无论是否为守护线程,会等待该线程执行完毕。
多个线程同时对某个数据读取或修改,出现不可预料的结果。GIL:Global Interpreter Lock,全局解释器锁。加锁的线程把锁释放,其他线程才能继续加锁并对数据做修改,完毕后释放锁,避免多个线程不会同时读取修改同一个数据,所以多线程不能发挥多核优势。
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global count
lock.acquire()
temp=count+1
time.sleep(0.001)
count=temp
lock.release()
lock=threading.Lock()
(联想,数值模拟斯托克斯参量,A方法调用B方法最近一次迭代的值,A的结果错误地与B接近)
多进程multiprocessing,每个进程都有属于自己的GIL,多核处理器中多进程的运行不受GIL影响。有优势。但由于进程是资源分配调度的一个独立单位,各进程间全局变量无法共享。
API调用
Process([group[,target[,name[,args[,kwargs]]]]])
target:传入方法名;
args:位置参数元组
若target的函数有两个参数m,n,则args传入[m,n]
kwargs:表示调用对象的字典。
name:为进程取名。
group:分组
def process(index):
time.sleep(index)
print(f'Process:{index}')
if __name__=='__main__':
for i in range(5):
p=multiprocessing.Process(target=process,args=(i,)) #(i,)逗号表示元组
p.start()
print(f'{multiprocessing.cpu_count()}')
for p in multiprocessing.active_children():
print(f'{p.name} id:{p.pid}')
print('Process Ended')
cpu_count方法返回当前机器CPU的核心数量
active_children方法返回当前还在运行的所有进程
结果:
Process Ended
Process:1
Process:2
Process:3
……
先Process Ended,再结束睡眠。
继承Process类
class MyProcess(Process):
def __init__(self,loop):
Process.__init__(self)
self.loop=loop
def run(self):
for count in range(self.loop):
time.sleep(1)
print(f'{self.pid} LoopCount:{count}')
if __name__=='__main__':
for i in range(2,5):
p=MyProcess(i)
p.start()
结果(竖着读结果):
(self.loop=2) 0 1
(self.loop=3) 0 1 2
(self.loop=4) 0 1 2 3
if __name__=='__main__':
for i in range(2,5):
p=MyProcess(i)
p.daemon=True#子进程为守护进程,避免孤立子进程的执行
p.start()
p.join()
终止进程terminate方法;is_alive方法判断进程是否仍在运行
if __name__ == '__main__':
p=multiprocessiing.Process(target=process)
print('Before:',p,p.is_alive())
p.start()
print('During:',p,p.is_alive())
p.terminate()
print('Terminate:',p,p.is_alive())
p.join()
print('Joined:',p,p.is_alive())
terminate()之后仍为alive,join()之后才为stopped。terminate无法立即终止,须用join令主程序等待其停止。
class MyProcess(Process):
def __init__(self,loop,lock):
Process.__init__(self)
self.loop=loop
self.lock=lock
def run(self):
for count in range(self.loop):
time.sleep(0.1)
#self.lock.acquire()
print(f'{self.pid} LoopCount:{count}')
#self.lock.release()
if __name__=='__main__'
lock=Lock()
for i in range(10,15):
p.MyProcess(i,lock)
p.start()
结果:有些打印输出未换行
取消代码的两行注释,进程互斥锁,结果每句打印均换行。
Semaphore信号量:控制临界资源的数量,实现多个进程同时访问共享资源,限制进程的并发量。
Queue队列:让进程共享数据。如果queue换为全局list,由于进程间的资源不共享,在A进程改变list,B不能获取该list的状态。
duplex管道:实现进程间通信。单向half-duplex一个进程发消息一个接收;双向duplex互相收发。单工双工。pipe默认为双向管道。
进程池Pool:请求会等待直到池中有进程结束,才会创建新的进程来执行它。
map方法:
pool.map(scrape,urls)
scrape只能传入单个url,map使urls批量被调用