python实战: request+pyqt5 小案例
1.案例目标简要说明
本次案例的需求主要分为两部分,一部分爬虫,一部分封装成小程序,下面详细介绍
1.1目标一:爬虫
1.1.1 进入目标网址:https://www.yaofangwang.com/
1.1.2. 如上图,输入编号搜索,如国药准字Z41021087
1.1.3. 进入如下网页,分别点击如下图红框,进入详情页
1.1.4. 按照价格升序排列,采集详情页中的如下红框的11项内容
(1)通用名称
(2)包装规格
(3)生产企业
(4)批准文号
(5)最低价格
(6)最低价格库存
(7)次低价格
(8)次低价格库存
(9)次次低价格
(10)次次低价格库存
(11)零售商家个数
1.2目标二:将爬虫封装成小程序
对爬虫功能封装成小程序,需实现能够可输入要排除的店铺名称,可输入采集时要排除的库存最低数量。
软件采集步骤
第一步:点击开始:导入需要采集的批准文号信息表格,只搜索表格是否含有批准文号字段,其他字段忽略
第二步:点击开始:软件开始自动运行,采集完毕后会自动到处csv文档,并展示在软件。
软件样式如下图
2.案例代码实现
2.1 爬虫代码关键步骤:
2.1.1 根据表格数据得到国标编号后,需根据搜索到的相应药品个数,判断页数。即根据如下图的紫色部分的个数除以每页展示的数据量,即可得到页数,并进行翻页处理。
2.1.2 进入各个药品详情页,需要根据价格升序排列,通过网页观察,如下图,只需对详情页url拼接相关参数即可。
2.1.3 需采集满足 排除店名,最小数量的前三低价格的数据:可利用if 语句判断店名,数量,符合条件的数据形成一个小列表,再将小列表append到一个大列表,判断大列表长度是否大于等于3,若大于等于跳出循环即可得到前三价格低的数据。
2.2 小程序关键步骤
基于pyqt5编写,不知道写啥,也是边学视频边瞎写的,大家自行看代码啦。业余选手,代码写比较丑,见谅!
整合代码:
-
测试表格:
我这随便打了几行数据,只要表格包含国标编号这一列就可。
-
如下代码定义了3个类,分别为**(1)MainWindow:小程序主体**,(2)InputQDialog:点击开始弹出供用户输入表格路径的窗口,(3)NewTaskTread:执行爬虫的线程
import sys
import os
import pandas as pd
import math
import requests
from lxml import etree
from fake_useragent import UserAgent
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QPushButton, QLineEdit, QTableWidget, QTableWidgetItem, QLabel
from PyQt5.QtWidgets import QMessageBox, QDialog
BASE_DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
# 小程序主题框架
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
# 初始化 包含init_header(功能区)及init_form(表格区),两个纵向排列的区域
def init_ui(self):
self.setWindowTitle('药房网采集系统')
self.resize(1450, 450)
self.terminate = False
layout = QVBoxLayout()
layout.addLayout(self.init_header())
layout.addLayout(self.init_form())
self.setLayout(layout)
self.show()
# 功能区(包括:排除店名,排除数量,开始,停止)
def init_header(self):
input_layout = QHBoxLayout()
label_exclude = QLabel("排除的店名:", self)
input_layout.addWidget(label_exclude)
self.input_exclude_name = input_exclude_name = QLineEdit()
input_layout.addWidget(input_exclude_name)
label_exclude = QLabel("最低库存:", self)
input_layout.addWidget(label_exclude)
self.input_exclude_court = input_exclude_court = QLineEdit()
input_layout.addWidget(input_exclude_court)
input_layout.addStretch()
btn_start = QPushButton('开始')
btn_start.clicked.connect(self.event_btn_start)
input_layout.addWidget(btn_start)
btn_end = QPushButton('停止')
btn_end.clicked.connect(self.event_btn_end)
input_layout.addWidget(btn_end)
input_layout.addStretch()
return input_layout
# 表格区,用于数据展示
def init_form(self):
form_layout = QHBoxLayout()
self.tab = tab = QTableWidget(0, 12)
table_header = [
{'field': 'id', 'text': '序号', 'width': 80},
{'field': 'name', 'text': '商品名称', 'width': 120},
{'field': 'company', 'text': '生产厂家', 'width': 200},
{'field': 'spice', 'text': '商品规格', 'width': 200},
{'field': 'literacy', 'text': '批准文号', 'width': 100},
{'field': 'min_price', 'text': '最低价', 'width': 100},
{'field': 'min_price_num', 'text': '盒数', 'width': 100},
{'field': 'mmin_price', 'text': '次底价', 'width': 100},
{'field': 'mmin_price_num', 'text': '盒数', 'width': 100},
{'field': 'mmmin_price', 'text': '次次低价', 'width': 100},
{'field': 'mmmin_price_num', 'text': '盒数', 'width': 100},
{'field': 'company_num', 'text': '零售厂家数量', 'width': 100},
]
for idx, info in enumerate(table_header):
item = QTableWidgetItem()
item.setText(f'{info["text"]}')
tab.setHorizontalHeaderItem(idx, item)
tab.setColumnWidth(idx, info["width"])
form_layout.addWidget(tab)
return form_layout
# 点击开始,弹出让用户输入表格路径的小窗口
def event_btn_start(self):
self.terminate = False
dialog = InputQDialog()
dialog.setWindowModality(Qt.ApplicationModal)
dialog.listHao.connect(self.init_task_listHao_callback)
dialog.exec_()
# 停止,将停止标识改为True
def event_btn_end(self):
self.thread.terminter = True
# 小窗口点击保存后执行这个,用于创建爬虫线程,执行爬虫
def init_task_listHao_callback(self, list):
if not self.input_exclude_name.text().strip():
shopname = ''
else:
shopname = self.input_exclude_name.text().strip()
if not self.input_exclude_court.text().strip():
excount = 0
else:
try:
excount = int(self.input_exclude_court.text().strip())
except:
QMessageBox.warning(self, "错误", "请在排除的数量里输入一个整数")
D_list = list
print(D_list)
self.thread = thread = NewTaskTread(D_list, shopname, excount, self)
thread.success.connect(self.init_task_success_callback)
thread.error1.connect(self.init_task_error1_callback)
thread.error2.connect(self.init_task_error2_callback)
thread.start()
# 爬虫执行成功,用于展示数据
def init_task_success_callback(self, list):
current_row_count = self.tab.rowCount()
self.tab.insertRow(current_row_count)
cell = QTableWidgetItem(str(current_row_count+1))
self.tab.setItem(current_row_count, 0, cell)
for i, content in enumerate(list):
cell = QTableWidgetItem(str(content))
self.tab.setItem(current_row_count, i+1, cell)
# 失败,异常处理
def init_task_error1_callback(self, message):
print('error1:', message)
# 失败,异常处理
def init_task_error2_callback(self, message):
print('error2:', message)
# 点击开始,弹出下面的子输入框,供用户输入表格路径
class InputQDialog(QDialog):
listHao = pyqtSignal(list)
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.resize(300, 150)
self.setWindowTitle('导入表格配置')
layout = QVBoxLayout()
hider = QVBoxLayout()
label = QLabel("请输入想要导入的表格地址:", self)
hider.addWidget(label)
self.input_lint = input_lint = QLineEdit()
input_lint.setText("F:\桌面\国标编号text.xlsx")
hider.addWidget(input_lint)
Dlayout = QHBoxLayout()
Dlayout.addStretch()
btn_SAVE = QPushButton('保存')
btn_SAVE.clicked.connect(self.event_save_click)
Dlayout.addWidget(btn_SAVE)
layout.addLayout(hider)
layout.addLayout(Dlayout)
self.setLayout(layout)
# 根据路径读取表格数据,提取其中的“国标编号”列
def event_save_click(self):
path = self.input_lint.text().strip()
if not path:
QMessageBox.warning(self, "错误", "所填内容不能为空")
return
df = pd.read_excel(path)
ColNames = df.columns.tolist()
# print(ColNames)
if '国标编号' not in ColNames:
QMessageBox.warning(self, "错误", "所选表格不含国标编号的列名!")
return
list_hao = list(set([str(hao).strip() for hao in df['国标编号'].tolist()]))
self.listHao.emit(list_hao)
self.close()
# 输入框输入路径后,点击保存,创建如下线程类,线程执行爬虫
class NewTaskTread(QThread):
success = pyqtSignal(list)
error1 = pyqtSignal(str)
error2 = pyqtSignal(str)
def __init__(self, list, shopname, excount, *args, **kwargs):
super().__init__(*args, **kwargs)
self.list = list
self.shopname = shopname
self.excount = excount
self.terminter = False
def run(self):
ua = UserAgent() # 生成假的浏览器请求头,防止被封ip
user_agent = ua.random # 随机选择一个浏览器
headers = {
'User-Agent': user_agent,
}
for Approval_number in self.list:
print('正在爬取:', Approval_number)
# 搜索国标编号,得到翻页的页数
url_page = f'https://www.yaofangwang.com/search.html?keyword={Approval_number}'
r1_page = requests.get(url=url_page, headers=headers)
r1_page.encoding = "utf-8"
page_sourse1 = r1_page.text
treepage = etree.HTML(page_sourse1)
allCount = treepage.xpath('//span[@class="result"]/b/text()')[0]
allPage = int(int(allCount) / 40 + 1)
# 如果搜到的药品数量为零,打印异常
if allCount == '0':
mainmessage = '{}未有相关信息'.format(Approval_number)
self.error2.emit(mainmessage)
continue
# 翻页处理
for mainpage in range(1, allPage + 1):
url = f'https://www.yaofangwang.com/search.html?keyword={Approval_number}&page={mainpage}'
r1 = requests.get(url=url, headers=headers)
r1.encoding = "utf-8"
page_sourse1 = r1.text
tree1 = etree.HTML(page_sourse1)
li_list = tree1.xpath('//ul[@class="goodlist_search clearfix"]/li')
for j, li in enumerate(li_list):
# 若点击停止,self.terminter = True
if self.terminter:
return
try:
params = {
'sort': 'sprice',
'sorttype': 'asc'
}
detail_url = 'https://' + li.xpath('./div/a/@href')[0][2:]
r2 = requests.get(url=detail_url, params=params, headers=headers)
r2.encoding = "utf-8"
page_sourse2 = r2.text
tree2 = etree.HTML(page_sourse2)
name = tree2.xpath('//h1[@class="clearfix pr0"]/strong/span/text()')[0]
Packaging = \
tree2.xpath('//div[@class="info clearfix"]/dl[@class="clearfix"]/dd[3]/div/div/text()')[0]
Companies = tree2.xpath('//div[@class="info clearfix"]/dl[@class="clearfix"]/dd[5]/text()')[0]
count = tree2.xpath('//ul[@class="navul"]/li/a/text()')[0].split('个')[0]
page_num = math.ceil(int(count) / 10)
info = []
for page in range(1, page_num + 1):
detail_url2 = 'https://' + li.xpath('./div/a/@href')[0][2:] + f'p{page}/'
r3 = requests.get(url=detail_url2, params=params, headers=headers)
r3.encoding = "utf-8"
page_sourse3 = r3.text
tree3 = etree.HTML(page_sourse3)
li_list3 = tree3.xpath('//ul[@class="slist"]/li')
if len(info) >= 3:
break
for li2 in li_list3:
ShopName = li2.xpath('./div[@class="shop"]/p/a/@title')[0]
Stock = li2.xpath('./div[@class="info"]/p[3]/span/label/text()')[0]
Price = li2.xpath('./div[@class="sale"]/p/text()')[0].split('¥')[-1].strip()
if int(Stock) <= int(10):
continue
elif ShopName == 'asdfa':
continue
else:
info.append([Stock, Price])
csv_li = [name, Companies, Packaging, Approval_number]
for i in info:
csv_li.append(i[1])
csv_li.append(i[0])
if len(csv_li) < 10:
csv_li = csv_li + [''] * (10 - len(csv_li))
csv_li.append(count)
# print(csv_li)
self.success.emit(csv_li)
except:
message = '{}下第{}页第{}条数据爬取失败|'.format(Approval_number, mainpage, j + 1)
self.error1.emit(message)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
# window.show()
sys.exit(app.exec_())
结束语
上述代码已实现主要功能,但应该还有好多可以改进的地方,比如一些爬虫的异常处理,现在表格只是读取excel,等等,哈哈哈,不想搞了