实战|如何低成本训练一个可以超越 70B Llama2 的模型 Zephyr-7B
每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新、社区活动、学习资源和内容更新、开源库和模型更新等,我们将其称之为「Hugging News」。快来看看有哪些近期更新吧!
每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新、社区活动、学习资源和内容更新、开源库和模型更新等,我们将其称之为「Hugging News」。快来看看有哪些近期更新吧!
OpenSSL 是一种开源的加密库,提供了一组用于加密和解密数据、验证数字证书以及实现各种安全协议的函数和工具。它可以用于创建和管理公钥和私钥、数字证书和其他安全凭据,还支持
SSL/TLS
、
SSH
、
S/MIME
、
PKCS
等常见的加密协议和标准。
OpenSSL 的功能非常强大,可以用于构建安全的网络通信、加密文件和数据传输,还可以用于创建和验证数字签名、生成随机数等安全应用。它被广泛用于Web服务器、操作系统、网络应用程序和其他需要安全保护的系统中。
如上所示的链接则是该库的官方网站,读者可自行下载对应版本的
OpenSSL
库,并运行安装程序,该库默认会被安装在根目录下,通过点击下一步即可很容易的完成安装配置。
该库安装成功后我们可以打开
OpenSSL-Win32
根目录,在目录中
bin
目录是可执行文件,OpenSSL的运行需要依赖于这些动态链接库,在使用时需要自行将本目录配置到环境变量内,其次
include
头文件
lib
静态库文件,在使用时读者需要自行配置到开发项目中,如下图所示;
OpenSSL库其本身就是一种加密与解密算法库,运用该库我们可以实现各类数据的加解密功能,首先我们以简单的Base64算法为例对该库进行使用。
Base64算法是一种用于将二进制数据编码为
ASCII
字符的算法。该算法将三个字节的二进制数据转换成四个字符的
ASCII
字符串,使得数据在传输时能够避免出现非法字符、特殊字符等问题,同时也可以将二进制数据转换为文本形式,方便在文本协议中传输,但读者需要注意
Base64
编码虽然可以作为一种简单的加密方式,但是它并不是一种真正的加密算法,因为它只是将数据转换为另一种形式,而没有对数据进行加密处理。
在OpenSSL中,使用
Base64
加密可以调用
BIO_f_base64
函数实现,该函数是一种
BIO
过滤器,用于将数据进行
Base64
编码和解码,如下代码中笔者分别封装实现了这两种加解密方法,其中
base64Encode
接收一个字符串并将该字符串压缩为编码字符串保存,与之相反
base64Decode
则用于将压缩后的字符串恢复。
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")
// base64 编码
char* base64Encode(const char* buffer, int length, bool newLine)
{
BIO* bmem = NULL;
BIO* b64 = NULL;
BUF_MEM* bptr;
b64 = BIO_new(BIO_f_base64());
if (!newLine)
{
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
}
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_write(b64, buffer, length);
BIO_flush(b64);
BIO_get_mem_ptr(b64, &bptr);
BIO_set_close(b64, BIO_NOCLOSE);
char* buff = (char*)malloc(bptr->length + 1);
memcpy(buff, bptr->data, bptr->length);
buff[bptr->length] = 0;
BIO_free_all(b64);
return buff;
}
// base64 解码
char* base64Decode(char* input, int length, bool newLine)
{
BIO* b64 = NULL;
BIO* bmem = NULL;
char* buffer = (char*)malloc(length);
memset(buffer, 0, length);
b64 = BIO_new(BIO_f_base64());
if (!newLine)
{
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
}
bmem = BIO_new_mem_buf(input, length);
bmem = BIO_push(b64, bmem);
BIO_read(bmem, buffer, length);
BIO_free_all(bmem);
return buffer;
}
上述代码的使用也非常简单,如下所示我们通过传入一个
input
字符串,并将该字符串压缩后输出,接着再把该字符串解密后输出。
int main(int argc, char* argv[])
{
// flag == false 将编码数据压缩为一行,否则原格式输出
bool flag = false;
std::string input = "Hello lyshark!";
// 输出编码内容
char* encode = base64Encode(input.c_str(), input.length(), flag);
std::cout << "Base64 编码后: " << encode << std::endl;
// 输出解码内容
char* decode = base64Decode(encode, strlen(encode), flag);
std::cout << "Base64 解码后: " << decode << std::endl;
system("pause");
return 0;
}
运行上述代码,读者可看到如下图所示的输出效果;
重新回来聊Agent,前四章的LLM Agent,不论是和数据库和模型还是和搜索引擎交互,更多还是大模型和人之间的交互。这一章我们来唠唠只有大模型智能体的世界!分别介绍斯坦福小镇和Chatdev两篇论文。它们的共同特点是使用多个大模型智能体协同完成任务。
多智能相比单一智能体可能有以下的应用场景
- Generative Agents: Interactive Simulacra of Human Behavior
- https://github.com/joonspk-research/generative_agents
斯坦福小镇算是这几个月来看到的最有意思的大模型应用了,作者设计了虚拟的小镇环境,并在其中设计众多不同性格的虚拟智能体,完全基于LLM的生成能力,让众多AI们在小镇中开始了生活,思考和互动。
生活环境和经历塑造了每一个个体,AI也不例外,所以后面的介绍我们会围绕以下三个核心组件相关代码来展开
在以上组件的加持下,小镇中的智能体们会发生以下基础行为
这里我们把沙盒环境放到第一个部分,因为个人感觉如何定义环境,决定了
self.tiles[9][58] = {'world': 'double studio',
'sector': 'double studio', 'arena': 'bedroom 2',
'game_object': 'bed',
'spawning_location': 'bedroom-2-a',
'collision': False,
'events': {('double studio:double studio:bedroom 2:bed', None, None)}}
同时Maze还存储了一份倒排索引,也就是给定当前智能体当前的地址,需要返回在地图中对应的二维坐标,这样就可以规划智能体从当前位置到某个地点的行动路径。
self.address_tiles['<spawn_loc>bedroom-2-a'] == {(58, 9)}
记忆流的设计算是论文的一大核心,分成以下两个部分
话接上面的环境感知部分,智能体感知到了周边的环境是第一步,第二步就是用感知到的信息,去召回智能体相关的历史记忆。这里被召回的记忆,除了之前感知的环境和事件,还有思考记忆,思考后面会讲到。召回除了使用embedding相似度召回之外, 记忆召回加入了另外两个打分维度
时效性
和
重要性
。
其中
时效性打分
是一个指数时间衰减模块,给久远的记忆降权,哈哈时效性打分是个宝,在很多场景下都是相似度的好伴侣,实际应用场景中RAG真的不止是一个Embedding模型就够用的。
重要性
打分是基于大模型对每个记忆的重要程度进行打分。打分指令如下
最后在召回打分时,相似度,时效性,重要性进行等权加和。
智能体记忆流中存储的除了感知到的环境之外,论文还增加了一类很有趣的
思考
记忆。哈哈不由让我想起了工作中听到的一个梗"老板说不能只干活,你要多思考!!"
触发机制也很有insight,就是每个智能体会有一个重要性Counter,当近期智能体新观察到的各类事件的重要性打分之和超过某个阈值,就触发思考任务。哈哈今天你思考了么?没有的话来学习下智能是如何思考的,对打工人很有启发哟
Given only the information above, what are !<INPUT 1>! most salient high-level questions we can answer about the subjects grounded in the statements?
1)
What !<INPUT 1>! high-level insights can you infer from the above statements? (example format: insight (because of 1, 5, 3))
1.
最后生成的思考会存储入记忆流中,用于之后的行为规划或者再进一步的思考。
最后一个模块是行为规划(plan.py),也是最主要的模块,决定了智能体在每一个时间点要做什么,也是之智能体记忆流中的
第三种记忆类型
。
除了基于当前状态去生成下一步行为之外,论文比较有意思的是先
规划了智能体每一天的待办事项,然后在执行事项的过程中,进行随机应变
。从而保证了智能体在更长时间轴上连续行为的连贯性,一致性,和逻辑关联。
智能体每日待办事项是通过自上而下的多步拆解,使用大模型指令生成的(plan.py)
第一步,冷启动,根据任务特点,生成智能体的作息时间,如下
第二步,生成小时级别的事项规划。这里并非一次生成所有事项,而是每次只基于智能体的所有静态描述,包括以上生成的生活作息,个人特点等等(下图),和上一个生成事项,来规划下一个事项。模型指令是1-shot,输出事项和事项持续的事件
第三步,是把小时级的事项规划进行事项拆解,拆分成5-分钟级别的待办事项。模型指令同样是1-shot,模板给出一个小时级别任务的拆分方式,让模型去依次对每个小时的事项进行拆解,模型指令中1-shot的部分格式如下:
Today is Saturday May 10. From 08:00am ~09:00am, Kelly is planning on having breakfast, from 09:00am ~ 12:00pm, Kelly is planning on working on the next day's kindergarten lesson plan, and from 12:00 ~ 13pm, Kelly is planning on taking a break.
In 5 min increments, list the subtasks Kelly does when Kelly is working on the next day's kindergarten lesson plan from 09:00am ~ 12:00pm (total duration in minutes: 180):
1) Kelly is reviewing the kindergarten curriculum standards. (duration in minutes: 15, minutes left: 165)
2) Kelly is brainstorming ideas for the lesson. (duration in minutes: 30, minutes left: 135)
3) Kelly is creating the lesson plan. (duration in minutes: 30, minutes left: 105)
4) Kelly is creating materials for the lesson. (duration in minutes: 30, minutes left: 75)
5) Kelly is taking a break. (duration in minutes: 15, minutes left: 60)
6) Kelly is reviewing the lesson plan. (duration in minutes: 30, minutes left: 30)
7) Kelly is making final changes to the lesson plan. (duration in minutes: 15, minutes left: 15)
8) Kelly is printing the lesson plan. (duration in minutes: 10, minutes left: 5)
9) Kelly is putting the lesson plan in her bag. (duration in minutes: 5, minutes left: 0)
最终分钟级别的待办事项会作为智能体当日的主线行为,写入以上的记忆流中,在之后的每一次行为规划中,提醒智能体,当前时间要干点啥。
以当日长期行为规划为基础,智能体在按计划完成当日事项的过程中,会不时的感知周围环境。当出现新的观测事件时,智能体需要判断是否需要触发临时行为,并调整计划。这里主要分成两种临时行为:交流和行动。这两种行为的触发会基于智能体当前的状态,和大模型基于上文的指令输出,例如对于是否产生对话行为的判断
当智能体A,出现在当前智能体可以感知的环境范围内时,通过以上的环境感知模块,智能体的记忆流中会出现智能体A的当前行为。这时智能体会在记忆流中检索和智能体A相关的记忆,合并当前状态作为上文,使用大模型指令判断是否要发起和A的对话
如果判断需要发起对话,则触发对话模块进行交流,而交流是所有社会性行为产生的根本。
主要模块基本就说这么多,技术评估就不多说了,在智能体行为上论文验证了当前框架会产生一定的社会效应,包括信息会在智能体之间传播,智能体之间会形成新的关系,以及智能体间会合作完成任务等等。
论文也讨论了当前框架的一些不足,包括如何在更长时间周期上泛化,如何避免智能体犯一些低级错误,例如躺上有人的床哈哈哈哈~
个人感觉还需要讨论的是如何在当前的记忆流中衍生成更高级的,抽象的思考,以及对世界的认知。这些认知是否有更高效,结构化的存储和召回方式。只依赖反思和记忆流的线性存储可能是不够的。
- Communicative Agents for Software Development
- https://github.com/OpenBMB/ChatDev
哈哈如果说斯坦福小镇对标综艺桃花坞,那ChatDev就是对标令人心动的Offer。论文参考了斯坦福小镇的记忆流,CAMEL的
任务导向型对话
方案,通过智能体间对话协同完成特定
软件开发任务
。
论文把软件开发流程,抽象成多个智能体的对话型任务。整个开发流程分成
设计,编程,测试,文档编写
四个大环节,每个环节又可以拆分成多个执行步骤,其中每个步骤都由两个角色的智能体通过对话合作来完成,如下
在进入主要流程前,让我们先完成准备工作。开发流程的准备工作需要定义三类配置文件,源码提供了默认的配置文件,用户可以根据自己的需求,选择覆盖部分配置。配置文件从Top to Bottom分别是
以默认配置为例,任务链包含以下步骤:DemandAnalysis -> LanguageChoose -> Coding -> CodeCompleteAll -> CodeReview -> Test -> EnvironmentDoc -> Manual
参与智能体角色包括:CEO,CFO, CPO, CCO, CTO, programmer,Reviewer,Tester等
PhaseConfig:下钻到每个步骤,分别定义了每个步骤的prompt指令,以及参与的两个Agent角色。
RoleConfig:下钻到每个角色,分别定义了每个角色的prompt指令
初始化配置文件后我们进入软件开发的四个主要流程~
产品设计环节,负责把用户需求转化成项目方案,包括两个原子步骤:CEO和CPO进行需求分析和产品设计,CEO和CTO选择编程语言。考虑每个phase的实现其实是相似的,只不过参与智能体不同,以及phase对应的指令和多轮对话形式不同,这里我们只说CEO和CPO之间关于需求分析对话实现(role_play.py)~
这里融合了CAMEL的Inception Prompting和斯坦福小镇的记忆流和自我反思来完成任务导向的对话。
首先初始化参与phase的两个智能体角色,并生成初始prompt(Inception Prompt)。包括用户需求(task_prompt),本阶段的任务描述(phase_prompt)和两个智能体的角色描述(role_prompt)。
基于初始指令,之后两个智能体会通过对话相互为对方生成指令,而人工参与的部分只有最初的角色描述和任务描述,所以叫初始指令。产品涉及环节的具体指令如下,需求分析阶段的任务指令使用了few-shot,给出不同的产品形态例如图片,文档,应用等实现方式,并明确了对话的两个智能体的讨论主题,以及终止讨论的条件,即
确定产品形态
以下是论文附录给出的一个需求分析阶段的具体对话示例,不过对话终止符似乎变成了<END>
老实说感觉这里的memoery stream和上面小镇中实现的memory stream关联似乎并不是很大。看代码实现,对话是直接使用上文对话history作为输入,只有当输入上文长度超长的时候,会保留Inception prompt和最近的N轮对话......难道是我漏看了代码,如果是请评论指出 >.<
小镇中自我反思是为了产生更抽象,更高级的个人思考。而在这篇论文self-Reflection其实更像是会议总结模块,当多轮对话完成,但是并未出现对话停止<END>符号,这时可以触发总结模块,把前面的多轮对话作为上文,来总结对话得到的结论,用于后续步骤的进行,如下
编程环节包括两个基本步骤:后端写代码,和前端设计交互界面。编程环节最大的难点就是如何避免模型幻觉,最大正度保证代码的正确性,以及在多轮对话中如何进行复杂长代码的编写和修改。这里同样我们只说下后端编写代码这一个步骤。
代码编写步骤的核心指令如下,CTO智能体给程序员智能体的指令是:以面向对象的编程语言python为基础,先给出核心类和方法。程序员智能体会按照指令以markdown为语法进行代码和注释的编写。之后代码编写环节会循环执行N次多轮对话,不断对代码进行更新优化。
在指令的基础上,为了优化复杂代码的编写效果,论文在代码编写环节引入了version control环节。在每一步代码编写完成后,会使用difflib对两版代码进行比对,并从记忆流中删除旧版本的代码,这样对话会永远基于最新的代码版本进行,对最新代码进行不断更新。
测试环节包括两个基本步骤:代码评审和测试环节。
评审环节,程序员智能体会给评审智能体指令,让其对代码进行检查,例如是否有未实现的类或方法,以及整个项目是否符合用户需求等等(角色指令如下图),并给出评审建议。其次程序员之智能体会基于以上建议对代码进行调整。
测试环节是基于代码执行后出现的bug进行修复。论文在这里引入了Thought Instruction,有点类似Decomposed Prompt的任务拆分。因为如果直接基于代码执行bug让大模型进行修复,问题可能过于复杂导致模型无法直接修复,或者产生幻觉。因此通过多轮对话引入一步任务拆分,先经过TestErrorSummary步骤对测试bug的位置和产生原因进行总结,再基于以上总结进行代码调整。
文档生成环节就比较简单了,包括多个phase步骤,一个phase对应一类文档说明。这里使用了few-shot指令来引导智能体生成requirements.txt, README.MD等用户文档,以下是生成requirements.txt的指令示例
效果上ChatDev从CAMEL编程相关的任务中随机抽了70个任务进行测试,任务平均代码量是131行代码,4个文件,3个上游依赖库,说明ChatDev整体生成的软件还是偏简单,小型,不涉及复杂的设计。具体效果大家可以直接去Chatdev的代码库里给的生成案例感受下。在这样的代码复杂度下,ChatDev最终代码的执行成功率在86% ,平均任务完成时间在7分钟左右,且调用成本相对较低。
除了以上提到了两个比较火爆的多智能体协同应用,还有很多相关应用和开源实现,这里就不一一介绍了,感兴趣的同学可以去自己试试看
想看更全的大模型相关论文梳理·微调及预训练数据和框架·AIGC应用,移步Github >>
DecryPrompt
哈喽大家好,我是咸鱼
之前咸鱼写过几篇关于知网爬虫的文章,后台反响都很不错。虽然但是,咸鱼还是忍不住想诉苦一下
有些小伙伴文章甚至代码看都没看完,就问我 ”为什么只能爬这么多条文献信息?“(看过代码的会发现我代码里面定义了
papers_need
变量来设置爬取篇数),”为什么爬其他文献不行?我想爬 XXX 文献“(因为代码里面写的是通过【知网高级搜索中的文献来源】来搜索文章),或者是有些小伙伴直接把代码报错贴给我,问我咋回事
我觉得在网上看到别人的代码,不要一昧地拿来主义,复制粘贴就行了,你要结合你自己的本地环境对代码做适当地修改。比如定位 Xpath 元素路径,不通电脑或者说不同浏览器同一元素的 Xpath 路径有可能不是一样的,这个路径在我本地运行没问题,到了你那里就报错
当看别人的代码时,最好先搞清楚:
以我这几篇知网爬虫文章举例:
言归正传,咸鱼昨天收到一位粉丝私信说能不能根据【关键词】来搜索文献
今天这篇文章着重讲
如何分析网页结构然后使用 selenium 根据知网的关键词来搜索文献
。至于对搜索到的文献的爬取,本文不过多介绍,因为以前的文章已经写过了
我们先来看下如果要通过关键词搜索文献,该怎么操作?
知网:
中国知网 (cnki.net)
首先我们登录网站,点击【高级搜索】(也可以直接点击搜索框中的【主题】下拉选择)
然后我们点击【主题】——>选择【关键词】
输入要搜索的关键词(例如:数字普惠金融)然后点击【检索】
结合前面的需求分析,我们就可以对网页进行分析并定位出对应的元素
首先是【高级搜索】,高级搜索有一个链接:
高级检索-中国知网 (cnki.net)
,这样就能省掉一个步骤了
然后我们需要点击 【主题】,才会出现下拉框。在分析网页的时候我发现当出现下拉框时,标签
<div class="sort-list" style="display: none;">"
中的
style
属性由
"display: none;"
变成
"display: block;"
下拉框出现之后,我们需要定位到 【关键词】 这个标签
# 关键词 Xpath 路径或 CSS 选择器
//*[@id="gradetxt"]/dd[1]/div[2]/div[1]/div[2]/ul/li[3]
li[data-val="KY"]
接着找到【搜索框】的 Xpath 路径。这里是一个 input 元素,用于接收来自用户的数据
# 输入框
//*[@id="gradetxt"]/dd[1]/div[2]/input
往输入框传入数据之后,我们需要点击下面的【检索】按钮
# 检索
/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input
点击搜索之后我们把【文献条数】爬取下来
# 文献条数
/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[1]/div[1]/span[1]/em
selenium 是一个自动化测试工具,可以用来进行 web 自动化测试。其
本质是通过驱动浏览器,完全模拟浏览器的操作(比如跳转、输入、点击、下拉等)来实现网页渲染之后的结果
,可支持多种浏览器
爬虫中用到 selenium 主要是为了解决 requests 无法直接执行 JavaScript 代码等问题
导入相关库
import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.action_chains import ActionChains
创建浏览器对象
这里我用的是 Edge 浏览器
def webserver():
# get直接返回,不再等待界面加载完成
desired_capabilities = DesiredCapabilities.EDGE
desired_capabilities["pageLoadStrategy"] = "none"
# 设置微软驱动器的环境
options = webdriver.EdgeOptions()
# 设置浏览器不加载图片,提高加载速度
options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
# 创建一个微软驱动器
driver = webdriver.Edge(options=options)
return driver
爬取网页
其实逻辑并不难,就是先定位到各个元素然后用 selenium 来模拟我们人为点击浏览器的操作就行了
首先打开页面,等待个一两秒让网页完全加载
driver.get("https://kns.cnki.net/kns8/AdvSearch")
time.sleep(2)
然后然下拉框显示出来,前面我们提到:标签
<div class="sort-list" style="display: none;">"
中的
style
属性由
"display: none;"
变成
"display: block;"
时,就会出现下拉框
这里我们通过执行 js 脚本来修改里面的
style
属性
# 修改属性,使下拉框显示
opt = driver.find_element(By.CSS_SELECTOR, 'div.sort-list') # 定位下拉框
# 执行 js 脚本进行属性的修改; arguments[0]代表第一个属性
driver.execute_script("arguments[0].setAttribute('style', 'display: block;')", opt)
下拉框显示出来之后我们需要点击【关键词】,这样才会切换到关键词搜索
这里需要注意的是,当我在测试的时候发现下拉框加载是有问题的,这时候代码会报错说
Element <li data-val="KY">...</li> is not clickable at point (189, 249)
就会使得程序点击不了【关键词】
而且我还发现如果加载不完全的话,需要鼠标移动到下拉框那里,让下拉框完全加载。所以这里我使用了 selenium 中的
ActionChains
来模拟鼠标的操作
用 selenium 做自动化,有时候会遇到需要模拟鼠标操作才能进行的情况,比如单击、双击、点击鼠标右键、拖拽等等
selenium 给我们提供了一个类来处理这类事件——ActionChains
还有一点需要注意的是:如果鼠标只是移到【关键词】,下拉框其实还是不能正确加载出来,最好是移动到下拉框的最底部或者关键词后面的元素,这里我移动到【通讯作者】
# 【通讯作者】定位
/html/body/div[2]/div/div[2]/div/div[2]/div[1]/div[1]/div[2]/ul/li[8]
li[data-val="RP"]
下拉框加载完成之后,定位到【关键词】再点击
# 鼠标移动到下拉框
ActionChains(driver).move_to_element(driver.find_element(By.CSS_SELECTOR, 'li[data-val="RP"]')).perform()
# 找到[关键词]选项并点击
WebDriverWait(driver, 100).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, 'li[data-val="KY"]'))).click()
定位出搜索框,传入我们要搜索的关键词
# 传入关键字
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, '''//*[@id="gradetxt"]/dd[1]/div[2]/input'''))
).send_keys(keyword)
# 点击搜索
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input"))
).click()
搜索结果出来之后定位【文献条数】,获取对应的条数(text 标签)
# 获取总文献数和页数
res_unm = WebDriverWait(driver, 100).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[1]/div[1]/span[1]/em"))
).text
完整代码如下:
import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.action_chains import ActionChains
def webserver():
# get直接返回,不再等待界面加载完成
desired_capabilities = DesiredCapabilities.EDGE
desired_capabilities["pageLoadStrategy"] = "none"
# 设置微软驱动器的环境
options = webdriver.EdgeOptions()
# 设置浏览器不加载图片,提高速度
options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
# 创建一个微软驱动器
driver = webdriver.Edge(options=options)
return driver
def open_page(driver, keyword):
# 打开页面,等待两秒
driver.get("https://kns.cnki.net/kns8/AdvSearch")
time.sleep(2)
# 修改属性,使下拉框显示
opt = driver.find_element(By.CSS_SELECTOR, 'div.sort-list') # 定位元素
driver.execute_script("arguments[0].setAttribute('style', 'display: block;')", opt) # 执行 js 脚本进行属性的修改;arguments[0]代表第一个属性
# 鼠标移动到下拉框中的[通讯作者]
ActionChains(driver).move_to_element(driver.find_element(By.CSS_SELECTOR, 'li[data-val="RP"]')).perform()
# 找到[关键词]选项并点击
WebDriverWait(driver, 100).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, 'li[data-val="KY"]'))).click()
# 传入关键字
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, '''//*[@id="gradetxt"]/dd[1]/div[2]/input'''))
).send_keys(keyword)
# 点击搜索
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input"))
).click()
# 点击切换中文文献
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[3]/div[1]/div/div/div/a[1]"))
).click()
# 获取总文献数和页数
res_unm = WebDriverWait(driver, 100).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[1]/div[1]/span[1]/em"))
).text
# 去除千分位里的逗号
res_unm = int(res_unm.replace(",", ''))
page_unm = int(res_unm / 20) + 1
print(f"共找到 {res_unm} 条结果, {page_unm} 页。")
if __name__ == '__main__':
keyword = "数字普惠金融"
driver = webserver()
open_page(driver, keyword)
结果如下:
在开始之前,你需要安装 pandas 和 matplotlib 库。如果你还没有安装,可以使用以下命令进行安装
pip install pandas matplotlib
另外,为了在图表中显示中文,你需要下载并安装中文字体文件。这里我们使用宋体,你可以替换为其他中文字体。下载后,将字体文件路径替换到代码中的
font
变量中
# 设置中文字体
font = FontProperties(fname='C:\\Windows\\Fonts\\simhei.ttf', size=12) # 替换为你的中文字体文件路径和字体大小
我们假设有一个 Excel 文件,其中包含多个店铺的销售数据。每个店铺有多个订单,每个订单有一个销售数量。我们需要对每个店铺的销售数量进行求和,并按照销售数量降序排列。以下是数据准备的代码:
import pandas aspd
# 从Excel文件中读取数据
data = pd.read_excel('C:\\Users\Admin\\Desktop\\数据核对\\新建 XLSX 工作表.xlsx')
# 聚合数据
aggregated_data = data.groupby('店铺名称')['销售数量'].sum()
# 按销售数量降序排列
aggregated_data = aggregated_data.sort_values(ascending=False)
print(aggregated_data)
首先,我们使用 pandas 库的
read_excel
函数读取 Excel 文件中的数据。然后,使用
groupby
函数对数据进行聚合,按照店铺名称分组,并对每个组中的销售数量求和。最后,使用
sort_values
函数按照销售数量降序排列。
接下来,我们使用 matplotlib 库绘制柱状图。以下是绘制柱状图的代码:
import matplotlib.pyplot asplt
# 绘制柱状图
aggregated_data.plot(kind='bar', color='steelblue', edgecolor='black', width=0.8)
# 设置图表标题和坐标轴标签
plt.title('店铺销售数量')
plt.xlabel('店铺名称')
plt.ylabel('销售数量')
# 显示图表
plt.show()
我们使用
plot
函数绘制柱状图,其中
kind
参数指定图表类型为柱状图,
color
参数指定柱子的颜色,
edgecolor
参数指定柱子边框的颜色,
width
参数指定柱子的宽度。然后,使用
title
、
xlabel
和
ylabel
函数设置图表标题和坐标轴标签。最后,使用
show
函数显示图表。
为了更清楚地展示每个店铺的销售数量,我们可以在柱子上添加数据标签。以下是添加数据标签的代码:
# 添加数据标签
for i, v inenumerate(aggregated_data):
plt.text(i, v, str(v), ha='center', va='bottom')
# 显示图表
plt.show()
我们使用
text
函数添加数据标签,其中
i
和
v
分别表示柱子的索引和高度,
ha
参数指定水平对齐方式为居中,
va
参数指定垂直对齐方式为底部。最后,再次使用
show
函数显示图表。
由于店铺名称较长,如果全部显示会导致刻度标签重叠,影响美观和可读性。因此,我们可以旋转刻度标签,并设置字体大小和字体样式。以下是设置刻度标签的代码:
# 设置刻度标签的字体大小和旋转角度
plt.xticks(rotation=45, fontsize=10)
# 显示图表
plt.show()
我们使用
xticks
函数设置刻度标签,其中
rotation
参数指定旋转角度为45度,
fontsize
参数指定字体大小为10。
我们可以添加图例,以便更好地解释图表中的信息。以下是添加图例的代码:
# 设置图例
plt.legend(['销售数量'], loc='upper right')
# 显示图表
plt.show()
我们使用
legend
函数添加图例,其中
loc
参数指定图例位置为右上角,
['销售数量']
表示图例中的文本。
为了让图表更加简洁和美观,我们可以去除上边框和右边框,并添加水平虚线网格线。以下是去除边框和添加网格线的代码
# 去除上边框和右边框
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
# 添加网格线
plt.grid(axis='y', linestyle='--', alpha=0.5)
# 显示图表
plt.show()
我们使用
gca
函数获取当前轴对象,然后使用
spines
属性去除上边框和右边框。使用
grid
函数添加水平虚线网格线,其中
axis
参数指定网格线方向为垂直方向,
linestyle
参数指定网格线样式为虚线,
alpha
参数指定网格线透明度为0.5。
最后,我们可以设置图表的背景色,并调整图表布局使得图表内容更加紧凑。以下是设置背景色和调整布局的代码:
# 设置背景色
plt.gca().set_facecolor('#F5F5F5')
# 调整图表布局
plt.tight_layout()
# 显示图表
plt.show()
我们使用
set_facecolor
函数设置背景色为浅灰色,使用
tight_layout
函数调整图表布局。
将上面的代码整合起来,得到完整的代码如下:
import pandas aspd
import matplotlib.pyplot asplt
# 从Excel文件中读取数据
data = pd.read_excel('C:\\Users\Admin\\Desktop\\数据核对\\新建 XLSX 工作表.xlsx')
# 聚合数据
aggregated_data = data.groupby('店铺名称')['销售数量'].sum()
# 按销售数量降序排列
aggregated_data = aggregated_data.sort_values(ascending=False)
# 绘制柱状图
aggregated_data.plot(kind='bar', color='steelblue', edgecolor='black', width=0.8)
# 添加数据标签
for i, v inenumerate(aggregated_data):
plt.text(i, v, str(v), ha='center', va='bottom')
# 设置图表标题和坐标轴标签
plt.title('店铺销售数量')
plt.xlabel('店铺名称')
plt.ylabel('销售数量')
# 设置刻度标签的字体大小和旋转角度
plt.xticks(rotation=45, fontsize=10)
# 设置图例
plt.legend(['销售数量'], loc='upper right')
# 去除上边框和右边框
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
# 添加网格线
plt.grid(axis='y', linestyle='--', alpha=0.5)
# 设置背景色
plt.gca().set_facecolor('#F5F5F5')
# 调整图表布局
plt.tight_layout()
# 显示图表
plt.show()