xpath基本概念
1)树:整个html(xml)代码结构就是一个树结构
2)节点:树结构中的每一个元素(标签)就是一个节点
3)根节点(根元素): html或者xml最外面的那个标签(元素)
4)节点内容:标签内容
5)节点属性:标签属性
xml数据格式
xml和json一样,是一种通用的数据格式(绝大部分编程语言都支持的数据格式)
xml是通过标签(元素)的标签内容和标签属性来保存数据的。
"""
示例:保存一个超市信息
1)json数据
{
"name": "永辉超市",
"address": "肖家河大厦",
"staffs":[
{"name":"小明", "id": "s001", "position": "收营员", "salary": 4000},
{"name":"小花", "id": "s002", "position": "促销员", "salary": 3500},
{"name":"张三", "id": "s003", "position": "保洁", "salary": 3000},
{"name":"李四", "id": "s004", "position": "收营员", "salary": 4000},
{"name":"王五", "id": "s005", "position": "售货员", "salary": 3800}
],
"goodsList":[
{"name": "泡面", "price": 3.5, "count": 120, "discount":0.9},
{"name": "火腿肠", "price": 1.5, "count": 332, "discount":1},
{"name": "矿泉水", "price": 2, "count": 549, "discount":1},
{"name": "面包", "price": 5.5, "count": 29, "discount":0.85}
]
}
xml数据:
小明
收营员
4000
小花
促销员
3500
张三
保洁
3000
李四
收营员
4000
王五
售货员
3800
泡面
3.5
120
火腿肠
1.5
332
矿泉水
2
549
面包
5.5
29
"""
1)创建树结构获取树的根节点
etree.XML(xml数据)
etree.HTML(html数据)
from lxml import etree
f = open('files/data.xml', encoding='utf-8')
root = etree.XML(f.read())
f.close()
2)根据xpath获取指定标签
节点对象.xpath(路径) - 返回路径对应的所有的标签,返回值是列表,列表中的元素是标签对象(节点
对象)
路径的写法:
注意:绝对路径和全路径的写法以及查找方式和是用谁去点的xpath无关
result = root.xpath('/supermarket/staffs/staff/name/text()')
print(result)
result = root.xpath('./staffs/staff/name/text()')
print(result)
staff1 = root.xpath('./staffs/staff')[0] # 获取第一个员工对应的staff标签
result = staff1.xpath('./name/text()')
print(result) # ['小明']
result = staff1.xpath('../staff/name/text()')
print(result) # ['小明', '小花', '张三', '李四', '王五']
result = root.xpath('//name/text()')
print(result)
result = staff1.xpath('//goods/name/text()')
print(result)
3)获取标签内容
节点对象.xpath(获取标签的路径/text()) - 获取指定路径下所有标签的标签内容
result = root.xpath('//position/text()')
print(result)
4)获取标签属性值
节点对象.xpath(获取标签的路径/@属性名)
result = root.xpath('/supermarket/@name')
print(result) # ['永辉超市']
result = root.xpath('//staff/@id')
print(result)
5)谓语(条件)
a. 位置相关谓语
[N] - 第N个
[last()] - 最后一个
[last()-N]; [last()-1] - 倒数第2个 、 [last()-2] - 倒数第3个
[position()>N]、[position()
result = root.xpath('//staff[1]/name/text()')
print(result) # ['小明']
result = root.xpath('//staff[last()]/name/text()')
print(result) # ['王五']
result = root.xpath('//staff[last()-1]/name/text()')
print(result) # ['李四']
result = root.xpath('//staff[position()<3]/name/text()')
print(result) # ['小明', '小花']
b.属性相关谓语
[@属性名=属性值] - 指定属性是指定值的标签
[@属性名] - 拥有指定属性的标签
result = root.xpath('//staff[@class="c1"]/name/text()')
print(result)
result = root.xpath('//staff[@id="s003"]/name/text()')
print(result)
result = root.xpath('//goods[@discount]/name/text()')
print(result)
c.子标签内容相关谓语 - 根据子标签的内容来筛选标签
[子标签名>数据]
[子标签名<数据]
[子标签名>=数据]
[子标签名<=数据]
[子标签名=数据]
result = root.xpath('//goods[price=2]/name/text()')
print(result)
6)通配符 - 写路径的时候用*来表示所有标签或者所有属性
result = root.xpath('//staff[1]/*/text()')
print(result)
# *[@class="c1"] == .c1
result = root.xpath('//*[@class="c1"]/name/text()')
print(result)
result = root.xpath('//goods[@*]/name/text()')
print(result)
result = root.xpath('/supermarket/@*')
print(result)
7)若干路径 - |
路径1|路径2 - 同时获取路径1和路径2的内容
result = root.xpath('//goods/name/text()|//staff/position/text()')
print(result)
import requests
from lxml import etree
import csv
def get_html():
headers = {
'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
url='https://movie.douban.com/top250'
response = requests.get(url=url,headers=headers)
return response.text
def get_data(html:str):
root = etree.HTML(html)
name_list = root.xpath('//ol[@class="grid_view"]/li/div/div/div/a/span[1]/text()')
# plot_list = root.xpath('//div[@class="bd"]/p/text()')
comment_list = root.xpath('//span[@class="rating_num"]/text()')
discourse_list = root.xpath('//span[@class="inq"]/text()')
comment_count = root.xpath('//div[@class="star"]/span[4]/text()')
all_data = list(map(lambda i1,i2,i3,i4:[i1,i2,i3,i4],name_list,comment_list,comment_count,discourse_list))
with open('files/电影.csv','w',encoding='utf-8',newline='') as f:
writer = csv.writer(f)
writer.writerow(['电影名称','评分','评论数','简介'])
writer.writerows(all_data)
if __name__ == '__main__':
data = get_html()
get_data(data)
1)进程和线程
进程: 一个正在运行的应用程序就是一个进程,每个进程均运行在其专门且受保护的内存空间中
线程: 线程是进程执行任务的基本单元(一个进程中的任务都是在线程中执行的)
进程就是车间,线程就是车间里面的工人。
一个进程中默认有一个线程,这个线程叫主线程。
2)线程的特点
如果在一个线程中执行多个任务,任务是串行执行的。
(当一个程序中有很多个任务的时候,如果只有一个线程,那么程序的执行效率会很低)
3)多线程
一个进程中有多个线程就是多线程。
多线程执行任务的时候,多个任务可以同时(并行)执行。
4)多线程原理
一个cpu同一时间只能调度一个线程,多线程其实是cpu快速的在多个线程之间进行切换,造成多个线程
同时执行的假象。
(提高cpu利用率)
一个进程默认只有一个线程,这个线程叫主线程,主线程以外的线程都叫子线程。
Python程序中如果需要子线程,必须创建线程类(Thread)的对象。
from threading import Thread
from time import sleep
from datetime import datetime
def download(name):
print(f'{name}开始下载:{datetime.now()}')
sleep(2)
print(f'{name}下载结束:{datetime.now()}')
if __name__ == '__main__':
# 情况1:在一个线程(主线程)中下载3个电影
# download('肖生克救赎')
# download('霸王别姬')
# download('阿甘正传')
# 情况2:使用3个子线程分别下载3个电影
# 1)创建线程对象
"""
线程对象 = Thread(target=函数, args=元组)
a.函数 - 可以是普通函数函数名,也可以是匿名函数。这个函数就是需要子线程中执行的任务。
b.元组 - 元组中的元素就是在子线程中调用target对应的函数的时候需要的参数
"""
t1 = Thread(target=download, args=('肖生克救赎',))
t2 = Thread(target=download, args=('霸王别姬',))
t3 = Thread(target=download, args=('阿甘正传',))
# 2)启动线程 - 让子线程调用对应的函数
t1.start()
t2.start()
t3.start()
import requests
from lxml import etree
from threading import Thread
from datetime import datetime
import csv
def get_html(page):
headers = {
'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
url=f'https://movie.douban.com/top250?start={page}&filter='
response = requests.get(url=url,headers=headers)
get_data(response.text)
def get_data(html:str):
root = etree.HTML(html)
name_list = root.xpath('//ol[@class="grid_view"]/li/div/div/div/a/span[1]/text()')
# plot_list = root.xpath('//div[@class="bd"]/p/text()')
comment_list = root.xpath('//span[@class="rating_num"]/text()')
discourse_list = root.xpath('//span[@class="inq"]/text()')
comment_count = root.xpath('//div[@class="star"]/span[4]/text()')
all_data = list(map(lambda i1,i2,i3,i4:[i1,i2,i3,i4],name_list,comment_list,comment_count,discourse_list))
print(all_data)
print(f'下载完成:{datetime.now()}')
with open('files/电影.csv','a',encoding='utf-8',newline='') as f:
writer = csv.writer(f)
writer.writerow(['电影名称','评分','评论数','简介'])
writer.writerows(all_data)
if __name__ == '__main__':
for page in range(0,256,25):
t = Thread(target=get_html,args=(page,))
t.start()
子线程对象.join() - 阻塞当前线程直到指定子线程任务完成
示例1: 三个电影都下载结束后打印"全部下载完成"
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
多线程爬取下载豆瓣数据
import requests
import csv
from bs4 import BeautifulSoup
from threading import Thread
from time import sleep
from datetime import datetime
f = open('file/多线程豆瓣.csv', 'a', encoding='utf-8', newline='')
writer = csv.writer(f)
writer.writerow(['名字', '分数', '评论人数', '介绍'])
def get_html(url):
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
response = requests.get(url=url, headers=headers)
return response.text
def download(page, url: str):
print(f'第{page}页开始下载:{datetime.now()}')
html = get_html(url)
soup = BeautifulSoup(html, 'lxml')
all_li = soup.select('.grid_view>li')
new_list = []
for li in all_li:
name = li.select_one('.item>.info>.hd .title').text
scores = li.select_one('.bd>.star>.rating_num').text
numbers = li.select_one('.item>.info>.bd>.star>span:nth-child(4)').text
info = li.select_one('.bd>.quote')
if info:
info = li.select_one('.bd>.quote').text.strip()
else:
info=''
new_list.append([name, scores, numbers, info])
print(new_list)
writer.writerows(new_list)
print(f'第{page}页下载完成:{datetime.now()}')
if __name__ == '__main__':
ts = []
for i in range(0, 51, 25):
url = f'https://movie.douban.com/top250?start={i}&filter='
t = Thread(target=download, args=(i, url))
ts.append(t)
t.start()
# 等待所有的子线程任务都结束后再接着执行
for x in ts:
x.join()
f.close()
线程池工作原理:先创建指定个数的线程,然后添加多个任务
(任务数量>线程数量),
让线程池中的线程去执行添加的所有任务,直到所有任务
都执行完(线程池中的每个线程可能会执行多个任务)
# 方案1:直接使用多线程下载1000个电影
# num = 0
# for _ in range(10):
# ts = []
# for x in range(100):
# num += 1
# t = Thread(target=download, args=(f'电影{num}',))
# ts.append(t)
# t.start()
# for x in ts:
# x.join()
# 方案2:使用线程池下载1000个电影
# 1. 创建线程池
# ThreadPoolExecutor(线程数最大值)
pool = ThreadPoolExecutor(3)
# 2. 添加任务
# 1) 一次添加一个任务: submit(函数, 实参1, 实参2, 实参3,...)
# 注意:实参的数量由前面的函数在调用的时候需要的实参来决定
pool.submit(download, '肖生克的救赎')
pool.submit(download, '霸王别姬')
# 2)同时添加多个任务: map(函数, 参数对应的序列)
# 注意:使用map添加多个任务的时候,任务对应的函数必须是有且只有一个参数的函数
pool.map(download, ['V字仇杀队', '恐怖游轮', '沉默的羔羊'])
# 3. 关闭线程池
# 线程池关闭后无法再添加新的任务,并且会阻塞当前线程等待整个线程池的任务都完成
pool.shutdown()
线程池爬豆瓣
import requests
from bs4 import BeautifulSoup
import csv
from concurrent.futures import ThreadPoolExecutor
def get_net_data(url: str):
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
}
proxies = {
'http': '121.206.253.171:4512',
'https': '121.206.253.171:4512'
}
response = requests.get(url, headers=headers, proxies=proxies)
# return response.text
analysis_data(response.text)
def analysis_data(html: str):
# 解析数据
soup = BeautifulSoup(html, 'lxml')
all_film_div = soup.select('.grid_view>li>.item')
all_data = [] # 保存每一页所有的电影
for div in all_film_div:
rank = div.select_one('.pic>em').text
name = div.select_one('.title').text
info = div.select_one('.bd>p').text.strip().split('\n')[-1].strip()
# time, country, category = info.split('/')
info_list = info.split('/')
time = info_list[0]
country = info_list[-2]
category = info_list[-1]
score = div.select_one('.rating_num').text
comment_count = div.select('.star>span')[-1].text[:-3]
intro_span = div.select_one('.inq')
if intro_span:
intro = intro_span.text
else:
intro = ''
all_data.append([int(rank), name, score, time.strip(), country.strip(), category.strip(), comment_count, intro])
films.append(all_data)
if __name__ == '__main__':
films = [] # 保存整个网站所有的电影
# [
# [[26, ], [], [], ...],
# [[1], [], [], ...],
# [[23], [], [], ...],
# ]
f = open('file/电影.csv', 'w', encoding='utf-8', newline='')
writer = csv.writer(f)
writer.writerow(['排名', '电影名称', '评分', '上映时间', '国家', '类型', '评论数', '简介'])
pool = ThreadPoolExecutor(10)
for page in range(0, 251, 25):
url = f'https://movie.douban.com/top250?start={page}&filter='
pool.submit(get_net_data, url)
pool.shutdown()
# 优化方案:按页排序
films.sort()
for x in films:
writer.writerows(x)
cd - 打开文件夹,后面直接拖过去
cd.. - 回到上一层
在打开之前要先切盘,window电脑: - 盘名:
dir - 查看文件夹内容ls(mac)
1)创建虚拟环境:
python -m venv 虚拟环境名
python3 -m venv 虚拟环境名
2)切换环境(激活环境)
虚拟环境目录\Scripts\activate.bat
退出虚拟环境 deactivate
1)pip - python包管理工具
2)pip install 第三方库名称 - 下载并且安装指定的第三方库
pip install 第三方库名称 -i 第三方库镜像地址
pip install 第三方库名称==版本号 -i 第三方库镜像地址
pip install 第三方库名称1 第三方库名称2 第三方库名称3
3)pip install -r 依赖文件 - 批量安装
Python国内镜像地址:
1.阿里云:https://mirrors.aliyun.com/pypi/simple/
2.豆瓣:https://pypi.douban.com/simple/
3.清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/
4.中国科学技术大学 https://pypi.mirrors.ustc.edu.cn/simple/
5.华中理工大学:https://pypi.hustunique.com/
6.山东理工大学:https://pypi.sdutlinux.org/
pip freeze > yilai.txt
常见的指令操作
执行指令的工具: Windows - 命令提示符(cmd) 、Mac - 终端
1. 运行python程序: - 运算程序的计算机必须先安装python环境
win: python py文件路径
mac: python3 py文件路径
2. 进入文件夹: cd
cd 文件夹相对路径、文件夹绝对路径
注意:如果是windows操作系统,cd操作如果要跨盘需要先切盘,然后再cd
切盘方法:C:、E:、D:
3. 查看当前文件夹的内容
win: dir
Mac:ls
4. 用指令创建虚拟环境
第一步:找到一个用来放虚拟环境的文件夹
第二步:通过cd指令进入到存放虚拟环境的文件夹中
第三步:创建虚拟环境
python -m venv 虚拟环境名
python3 -m venv 虚拟环境名
第四步:激活虚拟环境
(mac) source 虚拟环境目录/bin/activate
(windows) 虚拟环境目录\ Scripts\activate.bat
第五步:退出虚拟环境
deactivate
5.常用pip指令(pip - Python包管理工具)
pip list - 查看当前环境已经安装过的所有的第三方库
pip install 第三方库名称 - 下载并且安装指定的第三方库
pip install 第三方库名称 -i 镜像地址 - 在指定的镜像地址中下载安装
pip install 第三方库名称==版本号 -i 镜像地址
pip install 第三方库名称1 第三方库名称2
pip freeze > 依赖文件名 - 生成依赖文件
pip install -r 依赖文件路径 - 批量安装
pip uninstall 第三方库名称 - 卸载指定的第三方库
加油练习吧,下周是这个阶段最后一周了,期待全新的知识体系