爬虫概述
- 概念:是一种按照一定的规则,自动地抓取互联网上网页中相应信息(文本、图片等)的程序或脚本,然后把抓取的信息存储下来。
爬虫的分类
- 通用爬虫:把一整张页面源码数据进行爬取
- 聚焦爬虫:把页面中指定的数据进行爬取
- 增量式爬虫:监测网站数据更新的情况。
- 分布式爬虫:基于联合且分布的形式快速进行数据爬取
- 功能爬虫:刷评论,点赞等功能
反爬机制
作用在web的服务端。制定相关的机制来阻止爬虫对其网站数据的爬取。
- rebots 协议: 仅仅声明了门户端的哪些数据可被爬,哪些不可被爬。
反反爬机制
作用在爬虫,破解反爬机制。
requests
基础操作
- requests 是一个基于网络请求的模块。用来模拟浏览器发请求。
import requestsurl = 'https://www.sogou.com/'
# 发起get请求
response = requests.get(url=url)
page_text = response.text
with open('./sogou.html', 'w', encoding='utf-8') as fp:fp.write(page_text)
User-Agent
设置
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
}
url = 'https://www.sogou.com/web?query=杭州'
response = requests.get(url=url, headers=headers)
page_text = response.text
with open('./hang.html', 'w', encoding='utf-8') as fp:fp.write(page_text)# params 参数
wd = input("enter a keyword:")
params = {'query': wd}
url = 'https://www.sogou.com/web'
response = requests.get(url=url, headers=headers, params=params) # 参数动态化
page_text = response.text# 响应数据为json 则
page_info = response.json()
发送post请求
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
}
url = 'https://www.sogou.com/web?query=杭州'
data = {'page': 1, 'pageSize': 10}
response = requests.post(url=url, headers=headers, data=data)
page_text = response.json()# 参数为json形式
data = {'page': 1, 'pageSize': 10}
requests.post(url=url, headers=headers, json=data)
urllib
基于网络请求的模块
# 基于requests 的图片爬取
import requestsheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
}
img_data = requests.get("https://lmg.jj20.com/up/allimg/tp03/1Z9221U602IE-0-lp.jpg", headers=headers).content # content 返回bytes类型的数据
with open('./123.jpg', 'wb') as f:f.write(img_data)# 基于urllib的图片爬取
from urllib import requestrequest.urlretrieve("https://lmg.jj20.com/up/allimg/tp03/1Z9221U602IE-0-lp.jpg",'./234.jpg')
html 数据解析
bs4
基本使用
- 环境安装
pip install bs4 lxml
bs4
实现解析的流程:- 实例化一个BeautifulSoup的对象,需要把即将解析的页面源码数据加载到该对象中。
- 调用BeautifulSoup对象中相关的属性和方法进行标签定位和数据的提取
BeautifulSoup
对象的实例化BeautifulSoup(fp, 'lxml')
可以将fp表示的一个文件中的数据进行解析操作BeautifulSoup(page_text, 'lxml')
直接将请求到的页面源码数据进行解析操作
from bs4 import BeautifulSoupfp = open('./test.html', 'r', encoding='utf-8')
soup = BeautifulSoup(fp, 'lxml')## 标签定位
soup.title # <title>document</title>## 属性定位
soup.find('div', class_='tang') # 返回str
soup.find_all('div', class_='tang') # 返回列表## 选择器定位
soup.select('#feng') #返回列表
soup.select('.tang>ul>li') # 返回列表## 取文本内容
div_tag = soup.select('.tang')[0]
div_tag.text## 取属性
a_tag = soup.find('a', id='feng')
a_tag['href']
总结:
bs4的基本使用:
- 标签定位:
soup.tagName
: 只可以返回符合条件的第一个标签soup.find('tagName', attrName='value')
: 属性定位。返回值是匹配到的第一个元素soup.find_all('tagName', attrName='value')
: 返回匹配到的所以元素。返回列表soup.select('css 选择器')
: 返回列表
- 数据提取:
- 取标签中存储的数据
string
:只可以取出标签中直系的文本内容text
:可以取的标签中所有的文本
- 取标签属性的数据
tag['attrName']
- 取标签中存储的数据
xpath
解析
- 解析流程:
- 实例化一个
etree
对象,然后把即将被解析的数据加载到该对象中 - 调用
etree
对象中的xpath
方法结合着不同形式的xpath
表达式进行标签定位和数据提取
- 实例化一个
etree
对象的实例化etree.parse(filePath)
etree.HTML(page_text)
- 标签定位
- 非最左侧的
/
:表示一个层级 - 非最左侧的
//
:表示多个层级 - 最左侧的
/
:必须从根节点开始进行标签定位(/html
),表示绝对路径 - 最左侧的
//
:可以从任意位置进行标签定位,表示相对路径 **重点 - 属性定位的写法:
//tagName[@attrName="value"]
- 索引定位:
//tagName[index]
索引是从1开始的
- 非最左侧的
- 数据提取
- 提取标签数据
//text()
: 返回标签下所有节点的数据。包含子节点的文本内容/text()
:只可以返回直接子节点的文本内容
- 提取属性数据
/@attrName
:获取指定属性的值。返回列表
- 提取标签数据
from lxml import etreetree = etree.parse('./test.html') # ElementTree 类型的对象
tree.xpath('/html/head/title')[0]
tree.xpath('/html//title')[0]
tree.xpath('//title')[0]tree.xpath('//div[@class="song"]')
tree.xpath('//div[3]')## 数据提取
tree.xpath('a[@id="feng"]//text()')
tree.xpath('a[@id="feng"]/text()')tree.xpath('a[@id="feng"]//@href')
案例
from lxml import etreepage_text = requests.get("https://www.qiushibaike.com/text/", headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
}).texttree = etree.HTML(page_text)
div_list = tree.xpath('//*[@id="content"]/div/div[2]/div')
fot div in div_list:auther = div.xpath('./div[1]/a[2]/h2/text()')[0] # 局部解析xpath, . 表示当前divcontent = div.xpath('./a[1]/div/span//text()')content = ''.join(content)print(auther, content)
- xpath 管道符的使用
- 作用:可以使得xpath表达式具有更强的通用性
//div[1]/a/@href
|//div[1]/p/a/@href
requests
高级操作
Cookie在爬虫中的应用
- 手动处理cookie:直接将cookie放在请求头headers中
- 自动处理:Session对象
- session的创建:
requests.Session()
- session可以和requests一样调用get和post进行请求发送。如果使用session进行请求发送,则服务端响应后产生的cookie会被自动存储到session对象中。第二次请求时,自动携带存储的cookie。(即:session对象至少要发送两次请求)
- session的创建:
import requestssess = requests.Session()
sess.get("https://xueqiu.com/")
sess.get("https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=156113&size=15", headers=headers)
代理
import requests
from lxml import etreeheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'}# 配置代理
proxies = {'http': '102.129.157.20:8080'}
page_text = requests.get("https://www.sogou.com/web?query=ip",headers=headers, proxies=proxies).texttree = etree.HTML(page_text)
content = tree.xpath('//*[@id="ipsearchresult"]/strong//text()')
print(content)
代理池
import randomproxy_pool = [{'http': '102.129.157.20:8080'}, {'http': '102.129.157.20:8080'}]
page_text = requests.get("https://www.sogou.com/web?query=ip",headers=headers, proxies=random.choice(proxy_pool)).text
异步爬虫
线程池
import requests
from multiprocessing.dummy import Pool
import timeurls = ['http://localhost:5000/hello', 'http://localhost:5000/hello', 'http://localhost:5000/hello']
def getRequest(url):page_text = requests.get(url).textreturn page_textstart = time.time()
pool=Pool(3)
page_list = pool.map(getRequest, urls)
for page_text in page_list:print(len(page_text))print('总耗时:', time.time() - start)
生产者消费者模式
"""
生产者,消费者模式
"""
from threading import Thread
from queue import Queue
from urllib import requestfrom lxml import etree
import requestsclass Producer(Thread):headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ''Chrome/112.0.0.0 Safari/537.36'}def __init__(self, page_queue, img_queue, *args, **kwargs):super(Producer, self).__init__(*args, **kwargs)self.page_queue = page_queueself.img_queue = img_queuedef run(self) -> None:while True:if self.page_queue.empty():breakurl = self.page_queue.get()self.parse_page(url)def parse_page(self, url):response = requests.get(url, headers=self.headers)text = response.texthtml = etree.HTML(text)img_list = html.xpath('//*[@id="__layout"]//div[@class="expression-list clearfix"]//img')for img in img_list:img_url = img.xpath('./@data-src')[0]img_name = img.xpath('./@alt')[0] + '.jpg'print(img_name, img_url)self.img_queue.put((img_url, img_name))class Consumer(Thread):def __init__(self, page_queue, img_queue, *args, **kwargs):super(Consumer, self).__init__(*args, **kwargs)self.page_queue = page_queueself.img_queue = img_queuedef run(self) -> None:while True:if self.page_queue.empty() and self.img_queue.empty():breakimg_url, img_name = self.img_queue.get()request.urlretrieve(img_url, f"imgs/{img_name}")print(img_name + "\t 下载完成")def main():page_queue = Queue(3) # 存储页码链接的队列img_queue = Queue(10) # 存储解析出来的图片链接# 想要爬取 前 10页的数据for x in range(1, 4):url = f"https://www.doutub.com/img_lists/new/{x}"page_queue.put(url)for x in range(2):t = Producer(page_queue, img_queue)t.start()for x in range(2):t = Consumer(page_queue, img_queue)t.start()if __name__ == '__main__':main()
单线程+多任务的异步协程
- 特殊的函数
- 如果一个函数的定义被
async
关键字修饰,则该函数就是一个特殊的函数。 - 特殊函数调用后,返回了一个协程对象
- 函数内部的程序语句没有被立即执行
- 如果一个函数的定义被
- 协程
- 特殊函数调用后返回的即是协程对象
- 任务对象
asyncio.ensure_future(c)
- 事件循环:
eventLoop
asyncio.get_event_loop()
返回事件循环对象- 需要将任务对象注册到事件循环对象中,且启动事件循环即可
wait()
方法- 进行任务对象的挂起操作。
- 可以给每一个任务对象赋予一个可被挂起的权限
- 挂起:如果挂起一个任务对象就表示让当前正在被执行的任务对象交出cpu的使用权。
await
关键字:可以保证每一个任务对象的阻塞操作可以被异步执行- 核心点:如果特殊函数内部的实现语句中出现了不支持异步模块对应的代码,则会立即中断整个异步效果。
import asyncio
import timeasync def getRequest(url):print("start: ", url)time.sleep(2)print("end. ")return 'aaa'# 回调函数必须要有一个参数,就是回调函数的调用者(任务对象)
def callback(res, *args, **kwargs):# 特殊函数的返回值可以使用 result函数接受print("result: ", res.result())# 协程对象
c = getRequest("www.baidu.com")
# 任务对象
task = asyncio.ensure_future(c)
# 任务对象的高级之处: 可以给任务对象绑定回调函数
task.add_done_callback(callback)
# 事件循环对象
loop = asyncio.get_event_loop()
# 将任务对象放到事件循环对象中,并启动事件循环
loop.run_until_complete(task)
开启多任务异步协程
import asyncio
import timeasync def getRequest(url):print("start: ", url)# time.sleep(2) # 不支持异步模块的代码await asyncio.sleep(2)print("end. ")return 'aaa'# 回调函数必须要有一个参数,就是回调函数的调用者(任务对象)
def callback(res, *args, **kwargs):# 特殊函数的返回值可以使用 result函数接受print("result: ", res.result())start = time.time()
urls = ['www.baidu.com', 'www.jd.com', 'www.mi.com']
tasks = []
for url in urls:# 协程对象c = getRequest("www.baidu.com")# 任务对象task = asyncio.ensure_future(c)# 任务对象的高级之处: 可以给任务对象绑定回调函数task.add_done_callback(callback)tasks.append(task)# 事件循环对象
loop = asyncio.get_event_loop()
# 将任务对象放到事件循环对象中,并启动事件循环
loop.run_until_complete(asyncio.wait(tasks))
print("花费时间: ", time.time() - start) # 2.0086746215820312
aiohttp
支持异步请求操作的模块
pip install aiohttp
requests 模块不支持异步,需要用aiohttp
模块代替。异步协程爬虫代码案例:
import asyncio
import timeimport aiohttpasync def getRequest(url):# page_text = await requests.get(url).text # requests 不支持异步# 基于aiohttp 进行网络请求async with aiohttp.ClientSession() as sess: # 创建一个请求对象"""发起请求的操作:sess.get/post(url, headers, data/params, proxy='http://ip:port')"""async with await sess.get(url) as response:page_text = await response.text() # read() => content text() => textreturn page_text# 回调函数必须要有一个参数,就是回调函数的调用者(任务对象)
def callback(res, *args, **kwargs):# 此处进行解析html_str = res.result()print("result: ", len(html_str))start = time.time()
urls = ['https://www.baidu.com', 'https://www.jd.com', 'https://www.mi.com']
tasks = []
for url in urls:# 协程对象c = getRequest(url)# 任务对象task = asyncio.ensure_future(c)# 任务对象的高级之处: 可以给任务对象绑定回调函数task.add_done_callback(callback)tasks.append(task)# 事件循环对象
loop = asyncio.get_event_loop()
# 将任务对象放到事件循环对象中,并启动事件循环
loop.run_until_complete(asyncio.wait(tasks))
print("花费时间: ", time.time() - start)
Selenium
selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 。
selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器。
官网: https://selenium-python.readthedocs.io/
中文文档:https://selenium-python-zh.readthedocs.io/en/latest/
环境安装
- 下载安装selenium:
pip install selenium==3.141.0
- 下载浏览器驱动程序:
http://chromedriver.storage.googleapis.com/index.html - 下载驱动时,至少要保证大版本对应
基本操作
from selenium import webdriverfrom time import sleep# 1. 创建一个浏览器对象
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
# 2. 发起请求
bro.get("https://www.baidu.com/")
# 3. 标签定位
search_ele = bro.find_element_by_id("kw")
# 4. 节点交互
search_ele.send_keys("Mac Pro")
sleep(2)
btn_ele = bro.find_element_by_xpath('//*[@id="su"]')
btn_ele.click()
sleep(2)# js 注入
bro.execute_script('window.scrollTo(0, document.body.scrollHeight)')
sleep(5)bro.close()
相关方法
"""
## 1. 获取页面内容
bro.page_source## 2. 选择器(find系列)
# ===============所有方法===================
# 1、find_element_by_id # 通过id查找控件
# 2、find_element_by_link_text # 通过a标签内容找
# 3、find_element_by_partial_link_text # 通过a标签内容找,模糊匹配
# 4、find_element_by_tag_name # 标签名
# 5、find_element_by_class_name # 类名
# 6、find_element_by_name # name属性
# 7、find_element_by_css_selector # 通过css选择器
# 8、find_element_by_xpath # 通过xpaht选择器## 3. 获取元素属性
# 重点
# tag.get_attribute('href') # 找当前控件 的href属性对的值
# tag.text # 获取文本内容## 4. 元素交互
# tag.send_keys() # 往里面写内容
# tag.click() # 点击控件
# tag.clear() # 清空控件内容
"""
动作链
动作链这个工具类中封装好了一些基于浏览器自动化的一系列连续的行为动作(如:滑动,拖拽)
from selenium import webdriver
from time import sleepbro = webdriver.Chrome(executable_path="./chromedriver.exe")
url = 'https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'bro.get(url)# 这里定位的标签出现在了iframe标签之中,会定位失败,
# 需要使用 switch_to(frame的ID) 来切换到该frame
bro.switch_to.frame("iframeResult")
div_tag = bro.find_element_by_xpath('//*[@id="draggable"]')# 实例化动作链对象,且让该对象和bro对象进行绑定
action = webdriver.ActionChains(bro)
action.click_and_hold(div_tag) # 点击且长按
for i in range(5):action.move_by_offset(20, 30).perform() # perform 表示动作链立即执行sleep(1)sleep(2)
bro.close()
无可视化界面的浏览器操作
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time# 创建一个参数对象,用来控制chrome以无界面模式打开
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')# 创建浏览器对象
browser = webdriver.Chrome(executable_path='./chromedriver.exe', chrome_options=chrome_options)
browser.get('https://www.baidu.com/')
time.sleep(3)
# 进行截图
browser.save_screenshot('baidu.png')
browser.close()