WPF数据爬取小工具-某宝推广位批量生成,及订单爬取 记:接单最痛一次的感悟...

本文主要是介绍WPF数据爬取小工具-某宝推广位批量生成,及订单爬取 记:接单最痛一次的感悟...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

项目由来:上月闲来无事接到接到一个单子,自动登录 X宝平台,然后重定向到指定页面批量生成推广位信息;与此同时自动定时同步订单数据到需求提供方的Java服务。

当然期间遇到一个小小的问题就是界面样式的问题,起初使用的winform开发,但是样式,你懂的,所以后来索性直接使用wpf.

先声明:这里只做经验分享,不提供其他支持,毕竟,,,不安全。

  

1.首先看下我们的项目界面

 

说明:三张图分别是 登录,登录后主页面,和订单页面

  (登录页面)界面整体就划分上中下尾四个部分,种下部分的灰色是一个webBrowser.可以很好地帮助我们解决重定向之后,通过重定向页面获取cookie,这个后面回说。

    当然如果你觉得这个灰色很突兀,你可设置高宽为0,那么界面将会很简洁。我之所以显示出来是因为初次访问该网站的时候,会出现验证的问题,需要手动点击以及拖拽拼图。

  (主页面) 依旧是头部上部中部下部尾部,

  (订单页面)很明了。

  界面插件:MetroWindow,请自行百度,谢谢。

2.主要逻辑

  2.1.主页面内容

       首先我们分析下,一般情况下,我们在登录某平台时候,如果使用第三方授权登陆之后,地址中会有一个redirectUrl,即授权成功之后从定向的页面,那么此时我们要获取的cokkie肯定是从重定向之后的页面获取

   所以,这里也是一样的,我们这里的登录实现也是通过一个带有redirectUrl的登陆地址模拟post。

       首先,我们在窗体初始化的时候,在webBrowser中初始化我们的登录页面,也就是 灰色部分。然后通过webbrowser获取相关dom元素,赋值,模拟登陆按钮的提交事件,代码如下

     webBrowser代码:

       在窗体的load事件中初始化,其中的 LoginUrl 就是我们的 带有重定向地址的 登录地址;eg:https://login.xxbao.com/login?redirectURL=www.baidu.com

 webBrowser.Navigate(LoginUrl);

  当然如此还没完,如果了解webBrowser的人肯定都知道,这个东西有一个常用的事件就是 LoadCompleted,每当页面加载完成或者重定向完成之后,都会执行,所以,继续在load中添加如下的代码,将 LoadCompleted 事件先设置了,

webBrowser.LoadCompleted += (wbSender, wbArgs) =>{if (_loginViewModel != null && !string.IsNullOrWhiteSpace(_loginViewModel.LoginAccount)){if (wbArgs.Uri.ToString().Contains("登录成功之后,跳转到的回调的网站的主页面地址")){Log4netHelper.WriteLog(Log4netHelper.LogType.Info, "正在获取cookie...");// TODO 获取cookie操作try{_loginViewModel.StrCookies = CookieHelper.GetCookies(_loginViewModel.WebBrowser.Source.ToString());_loginViewModel._tb_token_ = CookieHelper.GetFiled(_loginViewModel.StrCookies, "_tb_token_");Log4netHelper.WriteLog(Log4netHelper.LogType.Info, "获取到TOKEN\n" + "\t" + _loginViewModel._tb_token_ + "");}catch (Exception ex){........错误记录省略}this.WriteLog("cookie成功获取,即将跳转到主页面...");this.GoTuiGuangWei();}}};}

  简要说明下:其中红色部分为登录成功之后重新定向的网站的主页面,之所以在这里判断,上面有说了,loadCompleted会在webBrowser每次页面加载完成的时候都会被执行,所以在这里我们判断当前加载的页面是否是我们想要从中获取

cookie的网站的页面,如果是,那么我们执行cookie的获取操作

  这里涉及到一个 重点问题就是cookie获取的问题,这里需要注意,下面提供的方法可以正常获取,其他方式 自行斟酌是否可行。

public class CookieHelper{[DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]static extern bool InternetGetCookieEx(string pchURL, string pchCookieName, StringBuilder pchCookieData, ref System.UInt32 pcchCookieData, int dwFlags, IntPtr lpReserved);[DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]static extern int InternetSetCookieEx(string lpszURL, string lpszCookieName, string lpszCookieData, int dwFlags, IntPtr dwReserved);private static string GetCookieString(string url){// Determine the size of the cookie     uint datasize = 256;StringBuilder cookieData = new StringBuilder((int)datasize);if (!InternetGetCookieEx(url, null, cookieData, ref datasize, 0x2000, IntPtr.Zero)){if (datasize < 0)return null;// Allocate stringbuilder large enough to hold the cookie     cookieData = new StringBuilder((int)datasize);if (!InternetGetCookieEx(url, null, cookieData, ref datasize, 0x00002000, IntPtr.Zero))return null;}return cookieData.ToString();}public static string GetCookies(string requestUrl){return GetCookieString(requestUrl);}/// <summary>///     从cookie中获取指定键名称的对应的值/// </summary>/// <param name="cookies"></param>/// <param name="fieldName"></param>/// <returns></returns>public static string GetFiled(string cookies, string fieldName){var cookieArray = cookies.Split(';');foreach (var cookieStr in cookieArray){if (cookieStr.Contains(fieldName)){return cookieStr.Split('=')[1];}}return string.Empty;}}

  涉及到的登录代码:

  

/// <summary>///     登录参数对象扩展方法/// </summary>public static class WebBrowserExtensions{/// <summary>///     登陆扩展/// </summary>/// <param name="loginViewModel"></param>public static void LoginEx(this LoginViewModel loginViewModel){var webBrowser = loginViewModel.WebBrowser;#region 验证操作(登陆一次之后就不存在了,但是这里个 domID不确定是不是这个)IHTMLDocument2 doc = (IHTMLDocument2)webBrowser.Document;try{IHTMLElement jsLoginCheck = doc.all.item("J_SafeLoginCheck", 0);//id或者是namejsLoginCheck.click();Thread.Sleep(1000);}catch (Exception){}#endregion//账号dom IDIHTMLElement elementAccount = doc.all.item("TPL_username_1", 0);//密码dom IDIHTMLElement elementPassword = doc.all.item("TPL_password_1", 0);//赋值操作elementAccount.setAttribute("value", loginViewModel.LoginAccount);//绑定值elementPassword.setAttribute("value", loginViewModel.LoginPassword);Thread.Sleep(100);IHTMLElement buttonSubmit = doc.all.item("J_SubmitStatic", 0);buttonSubmit.click();}}

  这里需要注意的是wpf的ewbBrowser和winform的稍有不同,获取dom的方式,通过  IHTMLDocument2 doc = (IHTMLDocument2)webBrowser.Document;,需要引用命名空间:using mshtml;

 

  2.2.主页面:

  后台代码中定义了两个计时器 (System.Timers.Timer),这里是 Timers下的timer不是Threading下的,注意下。其他细节不便提供出来,如果感兴趣的我可以把源码改过后开放出来,

  这里只说下控件数据绑定的问题,当多个线程同时操作某个控件时,虽说wpf的控件和winform有很大不同,但是一样的,存在子控件线程处理不当主线程(主界面)依旧会卡住的问题,

  所以我们可像下面这样解决:

  比如我们有一个label控件,定时刷新绑定 文本信息

  this.label.Invoke(new Action(()=>{
    this.Lable.Context ="我是好人";
  }));

  如此,是没有问题,但是看下面的写法,猜猜是否有问题呢?

  this.label.Invoke(new Action(()=>{

    //其他的请求操作,假设返回结果为 reuslt
    this.Lable.Context =result;
  }));

  不用怀疑,主线程会卡住,最明显的示例就是使用wpf的 RitchTextBox,如下代码,会存在很严重的 问题:

private void WriteLog(string message){

this.RitchTextBox.Dispatcher.BeginInvoke(new Action(() =>
{

       p = new Paragraph();

    Run r = new Run(message.ToString() + "\n");
    p.TextAlignment = TextAlignment.Left;
    p.Inlines.Add(r);

  RitchTextBox.Document.Blocks.Add(p);
  RitchTextBox.ScrollToEnd();
}));

}

   假如反复的执行该方法,去给 RitchTextBox 追加内容,界面会卡到你想把自己的蛋蛋捏碎,然而,成败就在细节之间,下面的方式 是毫无问题的,

private void WriteLog(string message)

{

     p = new Paragraph();

    Run r = new Run(message.ToString() + "\n");
    p.TextAlignment = TextAlignment.Left;
    p.Inlines.Add(r);

this.RitchTextBox.Dispatcher.BeginInvoke(new Action(() =>
{

  RitchTextBox.Document.Blocks.Add(p);
  RitchTextBox.ScrollToEnd();
}));

}

   当然更进一步的优化一下可以像下面的方式:

  

void WriteLog(object message, bool isError = false){this.Dispatcher.BeginInvoke(new Action(() =>{Paragraph p;if (isError){SolidColorBrush solidColorBrush = new SolidColorBrush(Color.FromRgb(255, 0, 0));p = new Paragraph() { Foreground = solidColorBrush };Run r = new Run(message.ToString() + "\n");p.TextAlignment = TextAlignment.Left;p.Inlines.Add(r);}else{p = new Paragraph();Run r = new Run(message.ToString() + "\n");p.TextAlignment = TextAlignment.Left;p.Inlines.Add(r);}this.rtbLog.Dispatcher.BeginInvoke(new Action(() =>{rtbLog.Document.Blocks.Add(p);rtbLog.ScrollToEnd();}));}));}

  其他:项目还使用了一个ini的文件的作为配置文件,相关实现类如下:

  

/// <summary>/// ini文件操作类/// </summary>public class IniHelper{#region 动态链接库调用/// <summary>/// 调用动态链接库读取值/// </summary>/// <param name="lpAppName">ini节名</param>/// <param name="lpKeyName">ini键名</param>/// <param name="lpDefault">默认值:当无对应键值,则返回该值。</param>/// <param name="lpReturnedString">结果缓冲区</param>/// <param name="nSize">结果缓冲区大小</param>/// <param name="lpFileName">ini文件位置</param>/// <returns></returns>[DllImport("kernel32")]private static extern int GetPrivateProfileString(string lpAppName,string lpKeyName,string lpDefault,StringBuilder lpReturnedString,int nSize,string lpFileName);/// <summary>/// 调用动态链接库写入值/// </summary>/// <param name="mpAppName">ini节名</param>/// <param name="mpKeyName">ini键名</param>/// <param name="mpDefault">写入值</param>/// <param name="mpFileName">文件位置</param>/// <returns>0:写入失败 1:写入成功</returns>[DllImport("kernel32")]private static extern long WritePrivateProfileString(string mpAppName,string mpKeyName,string mpDefault,string mpFileName);#endregion/// <summary>/// 读ini文件/// </summary>/// <param name="section">节</param>/// <param name="key">键</param>/// <returns>返回读取值</returns>public static string Ini_Read(string section, string key, string path){StringBuilder stringBuilder = new StringBuilder(1024);                  //定义一个最大长度为1024的可变字符串GetPrivateProfileString(section, key, "", stringBuilder, 1024, path);   //读取INI文件return stringBuilder.ToString();                                        //返回INI文件的内容}/// <summary>/// 写ini文件/// </summary>/// <param name="section">节</param>/// <param name="key">键</param>/// <param name="iValue">待写入值</param>public static void Ini_Write(string section, string key, string iValue, string path){WritePrivateProfileString(section, key, iValue, path);    //写入}/// <summary>/// 根据文件名创建文件/// </summary>/// <param name="path">文件名称以及路径</param>public static void ini_creat(string path){if (!File.Exists(path))                             //判断是否存在相关文件{FileStream _fs = File.Create(path);               //不存在则创建ini文件_fs.Close();                                    //关闭文件,解除占用}}/// <summary>/// 删除ini文件中键/// </summary>/// <param name="section">节名称</param>/// <param name="key">键名称</param>/// <param name="path">ini文件路径</param>public static void Ini_Del_Key(string section, string key, string path){WritePrivateProfileString(section, key, null, path);                          //写入}/// <summary>/// 删除ini文件中节/// </summary>/// <param name="section">节名</param>/// <param name="path">ini文件路径</param>public static void Ini_Del_Section(string section, string path){WritePrivateProfileString(section, null, null, path);                          //写入}/// <summary>///     指定的ini文件是否存在,不存在就创建/// </summary>/// <param name="iniFileName">文件名(非路径,只是名称)</param>/// <returns></returns>public static void Ini_Init(string iniFileName = "app.ini"){var filePath = IniFilePath(iniFileName);if (!File.Exists(filePath)){//初始化基础信息IniHelper.ini_creat(filePath);IniHelper.Ini_Write("INFO", "Preffix", "GWJ-", filePath);IniHelper.Ini_Write("INFO", "DaoGouID", "", filePath);IniHelper.Ini_Write("INFO", "Count", "0", filePath);IniHelper.Ini_Write("INFO", "TAG", "29", filePath);IniHelper.Ini_Write("INFO", "TimeRange", "8", filePath);//初始化 同步到Java 接口时候 爬取的分页,每爬取一次 更新一次IniHelper.Ini_Write("DELIVERY", "TO_PAGE", "1", filePath);IniHelper.Ini_Write("DELIVERY", "PER_PAGE_SIZE", "10", filePath);//IniHelper.Ini_Write("DELIVERY", "TIMES", "0", filePath);//剩余请求次数//初始化订单参数,IniHelper.Ini_Write("ORDER", "SIZE", "2000", filePath);//初始化 服务器地址配置var tempUrl = "http://xxxxx";var tgwAddress = IniHelper.Ini_Read("SERVER", "TGWAddress", filePath);if (tgwAddress != tempUrl){if (string.IsNullOrWhiteSpace(tgwAddress))IniHelper.Ini_Write("SERVER", "TGWAddress", tempUrl, filePath);//推广位elseIniHelper.Ini_Write("SERVER", "TGWAddress", tgwAddress, filePath);//推广位}var qq = "21321321";var initQQ = IniHelper.Ini_Read("QQ", "QQ", filePath);if (qq != initQQ){if (string.IsNullOrWhiteSpace(initQQ))IniHelper.Ini_Write("QQ", "QQ", qq, filePath);//客服QQ elseIniHelper.Ini_Write("QQ", "QQ", initQQ, filePath);//客服QQ }}}public static string IniFilePath(string iniFileName = "app.ini"){var filePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, iniFileName);return filePath;}}

 

  http帮助类:(context-type哪里可以合成一个方法

  

public class HttpRequestHelper{/// <summary>/// 默认的头/// </summary>public static string defaultHeaders = @"Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Accept-Encoding:gzip, deflate, sdchAccept-Language:zh-CN,zh;q=0.8Cache-Control:no-cacheConnection:keep-alivePragma:no-cacheUpgrade-Insecure-Requests:1User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36";public static string DoRequest(string alimamaUrl, string method, string postDataStr, Encoding encoding, string cookiesStr){var html = string.Empty;HttpWebRequest request = (HttpWebRequest)WebRequest.Create(alimamaUrl);request.Method = method;request.AllowAutoRedirect = true;request.ContentType = "application/x-www-form-urlencoded";request.KeepAlive = true;//request.CookieContainer.Add(cc);request.Headers[HttpRequestHeader.Cookie] = cookiesStr;if (method.ToUpper() == "POST"){byte[] data = Encoding.UTF8.GetBytes(postDataStr);request.ContentLength = data.Length;Stream requestStream = request.GetRequestStream();requestStream.Write(data, 0, data.Length);requestStream.Close();}try{HttpWebResponse httpResponse = (HttpWebResponse)request.GetResponse();using (System.IO.Stream dataStream = httpResponse.GetResponseStream()){using (System.IO.StreamReader sr = new System.IO.StreamReader(dataStream, Encoding.GetEncoding("utf-8"))){html = sr.ReadToEnd();sr.Close();}}httpResponse.Close();}catch (System.Net.WebException ex){html = ex.Message;}return html;}public static string DoJsonRequest(string alimamaUrl, string method, string postDataStr, Encoding encoding, string strCookies = ""){var html = string.Empty;HttpWebRequest request = (HttpWebRequest)WebRequest.Create(alimamaUrl);request.Method = method;request.AllowAutoRedirect = true;request.ContentType = "application/json";request.KeepAlive = true;//request.CookieContainer.Add(cc);if (!string.IsNullOrWhiteSpace(strCookies))request.Headers[HttpRequestHeader.Cookie] = strCookies;if (method.ToUpper() == "POST"){byte[] data = Encoding.UTF8.GetBytes(postDataStr);request.ContentLength = data.Length;Stream requestStream = request.GetRequestStream();requestStream.Write(data, 0, data.Length);requestStream.Close();}try{HttpWebResponse httpResponse = (HttpWebResponse)request.GetResponse();using (System.IO.Stream dataStream = httpResponse.GetResponseStream()){using (System.IO.StreamReader sr = new System.IO.StreamReader(dataStream, Encoding.GetEncoding("utf-8"))){html = sr.ReadToEnd();sr.Close();}}httpResponse.Close();}catch (System.Net.WebException ex){html = ex.Message;}return html;}}

  

3.注意点

   1.wpf窗体间传值问题;

   2.cookie(cookieCollection)的传递问题,在上面获取到cookie(是一个字符串),不用做任何处理,在后面每次的请求(使用http的帮助类),都需要带上,不需要转换成cookiCollection,直接拼接到header中即可(http帮助类中有做判断),

   3.控件绑定绑定值问题,避免子线程阻塞主线程(界面)

 

 源码稍后提供到git

 

 4.感悟:多么痛的领悟。

  自从业以来,打过工、创过业(尽管失败了),接过单,但是这次接的这破东西是最坑的一次,按以往的经验和习惯,就这样类似的东西,仅仅四五个功能的,基本3-4个小时不出意外的话,毫无疑问的就可以完成,其中会有1-2小时的测试和数据分析。

  然而,就这三个界面的东西,前前后后花了我三四天时间,每天都要花在上面3小时左右,平心而论,儿了这千把块的东西珍惜不值得,第二天时候我已经和需求方提出了,你们重新找人吧,需求一再的变动,前前后后不下4次,价格却一成不变,咱拜拜吧。但是一再的求我,

  “兄弟,帮个忙吧,好处会有的,,,,”,完全是一堆废话,后又赶上订亲等一些事宜是真的很忙,这人竟然和我火了,大发雷霆,发短信打电话威胁我,我说你这么嚣张威胁我?回:对,我就是威胁你。。。。如此之嚣张,无奈之举,,找了几个兄弟,

  同时报了警,做两手准备,钱可以不要,尊严一定要有,不能让技术显得这么廉价。,,,然而我想多了,事后第二天这人又打我电话表态与我和好,说自己是个傻子,让我把它当成个傻子,求我一定要把他这个东西做好,,,,

  抛开其他不说,格局已经定了,这就是格局,小的很,虽然这人和其他两人创业的,但是格局决定了这个人以后的道路。

  最后当然是把东西做完了,也就是上面的截图,交给了他,这人硬塞给了我1k,,,,1k,,,1k啊,尽管我早说了不要这钱了。

  我就是想说明下,不论单子大小,不论生人熟人(我接这活的人是一个老乡),一定先见到钱,其他的都是扯淡,生意上,有钱老子跟你混,没钱少跟老子扯淡。更不要跟我谈什么亲戚朋友或者是狗屁老乡,我跟钱才是老乡。

  其次是需求文档,无文档不开发,每次业务变更需求变动,白纸黑字,明明白白的写清楚,咱该加价加价,该加工期加工期。

 

  

 

转载于:https://www.cnblogs.com/Tmc-Blog/p/9876233.html

这篇关于WPF数据爬取小工具-某宝推广位批量生成,及订单爬取 记:接单最痛一次的感悟...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/271644

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

AI一键生成 PPT

AI一键生成 PPT 操作步骤 作为一名打工人,是不是经常需要制作各种PPT来分享我的生活和想法。但是,你们知道,有时候灵感来了,时间却不够用了!😩直到我发现了Kimi AI——一个能够自动生成PPT的神奇助手!🌟 什么是Kimi? 一款月之暗面科技有限公司开发的AI办公工具,帮助用户快速生成高质量的演示文稿。 无论你是职场人士、学生还是教师,Kimi都能够为你的办公文

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只