抓取单词音节 您所在的位置:网站首页 second拆分音节 抓取单词音节

抓取单词音节

2023-06-29 08:08| 来源: 网络整理| 查看: 265

抓取单词音节——用多台计算机加快抓取速度

 

新产品的单词拼写做了很大改动,这次加上了按照音节进行拼写练习的过程。所谓“按音节”,就是按照英文单词的发音规律把字母进行拆分。例如单词“construction”,发音为“康斯爪克深”,按照这个发音规律,可以把字母拆分为“con(康)”、“struc(斯爪克)”、“tion(深)”。这就是音节,按照这个规律可以很快记住并拼会一个单词。

 

现有的数据库中有存放单词的数据表all_word,这个表在一个月以前就加入了音节字段syllable,并且音节数据也都加进去了。音节数据是怎么加上的呢?是琛子写了一个PHP Shell脚本,到一个网站上去抓取音节文本,然后保存到数据库。这个网址是http://www.juiciobrennan.com/syllables/,在文本框里输入“construction”,单击提交按钮,就可以得到按音节拆分的结果“con-struc-tion”。这个网址可是省了研发部不少力气,因为按音节拆分单词的算法可是相当复杂的。

 

距离新产品上线时间越来越近了,Jin询问琛子数据是不是都已经准备好了?琛子说音节还需要再重新抓取一下。琛子是南方人,说话声音很轻,而且也不爱说话。琛子做事情非常谨慎,就算不在工作场合谈论工作话题时,也会左顾右盼的确定没有任何问题,才低声说一两句。Jin得知音节还需要再重新抓取感到有些不解,因为早在一个月前,音节数据就有了。琛子解释说,因为单词数据表添加了不少新内容。Jin同意让琛子重新抓取数据,最后还关切的问了一句:“大概什么时候能抓完?”,琛子说:“大概两天吧”。

Jin:“啊?用两天?!”

琛子:“上回抓数据,不也是用两天吗?”

琛子停顿了一会儿又说:“数据挺多的,8万多个单词”

Jin意识到抓取音节数据不能持续进行两天,这样会推迟测试时间导致项目无法如期上线!所以Jin向琛子提出了要求,希望能在两个小时内就完成抓取工作。

 

琛子目前也没有好的办法给程序提速,只能盼望自己的机器跑的快一点。Jin向琛子要来了音节抓取代码,源文件是用PHP写的,但是为了方便读者下载和调试,我使用C#语言进行复述。如下:

 

ContractedBlock.gif ExpandedBlockStart.gif 代码 using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Text.RegularExpressions; using MySql.Data.MySqlClient; namespace DOSApp0{ /// /// 主应用程序类 /// class MainApp { /// /// 目标 URL /// private const String TARGET_URL = " http://www.juiciobrennan.com/syllables/ " ; /// /// MySQL 连接字符串 /// private const String MYSQL_CONN = " Database=temp_test;Data Source=127.0.0.1;User Id=root;Password= " ; /// /// 获取单词列表 /// private const String SQL_GetWordList = " SELECT `english` FROM `all_word` " ; /// /// 更新单词音节 /// private const String SQL_UpdateWord = " UPDATE `all_word` SET `syllable` = @syllable WHERE `english` = @word " ; /// /// 应用程序主函数 /// /// static void Main( string [] args) { // 开始抓取 ( new MainApp()).Start(); Console.WriteLine( " press 'ENTER' exit " ); Console.ReadLine(); } /// /// 开始运行 /// public void Start() { IList wordList = this .GetWordList(); foreach (String word in wordList) { Console.Write(word); if ( ! this .CanGrab(word)) { Console.WriteLine( " --> No " ); continue ; } // 获取相应文本 String responseText = this .PostWord(word); if (String.IsNullOrEmpty(responseText)) { Console.WriteLine( " --> No " ); continue ; } // 提取音节 String syllable = this .ExtractSyllable(responseText); if (String.IsNullOrEmpty(syllable)) { Console.WriteLine( " --> No " ); continue ; } // 更新音节 this .UpdateWord(word, syllable); // 更新成功 Console.WriteLine( " --> Yes " ); } } /// /// 获取英文单词列表 /// /// private IList GetWordList() { // 创建连接 MySqlConnection sqlConn = new MySqlConnection(MainApp.MYSQL_CONN); // 创建命令 MySqlCommand sqlCmd = new MySqlCommand(MainApp.SQL_GetWordList, sqlConn); // 单词列表 List wordList = new List (); try { sqlConn.Open(); // 执行 SQL 查询 MySqlDataReader dr = sqlCmd.ExecuteReader(); while (dr.Read()) { // 添加单词到列表 wordList.Add(Convert.ToString(dr[ " english " ])); } } catch { throw ; } finally { sqlConn.Close(); } return wordList; } /// /// 是否可以抓取音节 /// /// /// private bool CanGrab(String word) { return ! ( new Regex( @" [^(\w)]+ " )).IsMatch(word); } /// /// 发送单词获取音节文本 /// /// /// private String PostWord(String word) { if (String.IsNullOrEmpty(word)) { return "" ; } // 创建 Web 请求 WebRequest request = WebRequest.Create(MainApp.TARGET_URL); // 获取发送内容 byte [] postContent = Encoding.UTF8.GetBytes(String.Format( " inputText={0} " , word)); request.ContentType = " application/x-www-form-urlencoded " ; request.ContentLength = postContent.LongLength; request.Method = " POST " ; // 获取请求流对象 Stream requestStream = request.GetRequestStream(); // 设置 POST 参数 requestStream.Write(postContent, 0 , postContent.Length); requestStream.Flush(); requestStream.Close(); // 获取响应 HttpWebResponse response = request.GetResponse() as HttpWebResponse; // 获取响应流对象 Stream responseStream = response.GetResponseStream(); // 创建文本读取流 StreamReader sr = new StreamReader(responseStream); return sr.ReadToEnd(); } /// /// 获取音节字符串 /// /// /// private String ExtractSyllable(String src) { if (String.IsNullOrEmpty(src)) { return "" ; } // 创建提取音节正则表达式 Regex syllableRegex = new Regex( @" (.*) " ); // 匹配 Match syllableMatch = syllableRegex.Match(src); if (syllableMatch == null ) { return "" ; } String syllable = syllableMatch.Value; if (String.IsNullOrEmpty(syllable)) { return "" ; } // 清除 html 标记 syllable = new Regex( @" ]*> " ).Replace(syllable, "" ); return syllable; } /// /// 更新单词音节 /// /// /// private void UpdateWord(String word, String syllable) { if (String.IsNullOrEmpty(word) || String.IsNullOrEmpty(syllable)) { return ; } // 创建连接 MySqlConnection sqlConn = new MySqlConnection(MainApp.MYSQL_CONN); // 创建命令 MySqlCommand sqlCmd = new MySqlCommand(MainApp.SQL_UpdateWord, sqlConn); // 音节 sqlCmd.Parameters.AddWithValue( " @syllable " , syllable); // 单词 sqlCmd.Parameters.AddWithValue( " @word " , word); try { sqlConn.Open(); sqlCmd.ExecuteNonQuery(); } catch { throw ; } finally { sqlConn.Close(); } } }}

 

Jin看到这个代码后,第一感觉是逻辑很清晰。总共就以下几步:

从数据库中取出所有单词,存入一个列表;从列表中读取一个单词,到指定网址抓音节;利用正则表达式提取音节字符串;更新数据库;重复第2步到第4步,直到所有单词全部抓取完;

流程图如图1所示:

 

 

2010031409532958.png

(图1)音节抓取程序流程图

 

虽然流程上很清晰没什么问题,但是Jin觉得这里面还是存在问题的。首先第一步,从数据库中取出所有单词,就不是一个很好的做法。all_word数据表里一共有8万多条记录,将这些记录全部读取出来,会占用很大内存,甚至是内存溢出。Jin刚出道的时候,就犯过这个错误。其次是第二步,从列表中读取一个单词,到指定网址抓音节。Jin考察了一下http://www.juiciobrennan.com/syllables/这个地址,这个地址允许提交多个单词。也就是说,可以在输入框里输入多个单词,用回车区分。换成程序方式就用WebRequest发送这样的数据“inputText=construction\r\nenglish\r\nchinese”。将数据积攒在一起批量发送,性能上要高于频繁发送零散数据,因为进行一次HTTP连接是相当昂贵的!

 

减少远程请求次数是提高系统性能的重要手段!但即便这样,还是无法满足Jin要求的两个小时内抓取完所有数据。对于要完成的任务不仅仅要考虑编码上的优化,还要考虑从部署上进行优化。琛子自己的机器负责跑程序抓取单词,而数据库是放在另外一个服务器上,部署视图如图2所示:

 

2010031409540558.png

(图2)部署视图

 

琛子不明白,抓音节怎么又扯到部署上?Jin解释说,如果摆在你面前一个大水缸,里面装的不是水都是米饭,现在我要求你两个小时内把米饭都吃光!让一个人在一个月左右吃光一缸米饭,应该很容易做到。但是让一个人在两个小时内吃完,那是根本不可能做到的,人早就被撑死了。要达到这样苛刻的要求,难道就一点办法都没有吗?当然是有!你不要真的傻到一个人去吃,要发动更多的人来帮你一起吃,人越多,吃的就越快越干净。正所谓人多力量大,众人吃饭热情高。再例如某个写字楼里的保洁人员,如果让一个保洁人员清扫整个大楼,那会被累死的。而实际情况是,每一个保洁负责一个楼层,甚至是多个保洁负责一个楼层。其核心思想就是将一个大块任务,分解成多个小任务,分派给多个执行单元同时进行处理。

 

那么回到音节抓取这个真实案例中,我们可以多启动几个程序,多找几台机器同时抓取。我们将数据分成几区段,不同的机器负责不同的区段,如图3所示:

 

2010031409543413.png

(图3)部署视图

 

Jin快速重构了代码,重构结果如下:

 

Word.cs

 

ContractedBlock.gif ExpandedBlockStart.gif 代码 using System; namespace DOSApp1{ /// /// 单词 /// public class Word { /// /// 获取或设置 ID /// public int ID { get ; set ; } /// /// 获取或设置英文 /// public String English { get ; set ; } /// /// 获取或设置音节 /// public String Syllable { get ; set ; } }}

 

Grabber.cs

 

ContractedBlock.gif ExpandedBlockStart.gif 代码 using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Net; using System.Text; using System.Text.RegularExpressions; using MySql.Data.MySqlClient; namespace DOSApp1{ /// /// 音节抓取者 /// public class Grabber { /// /// 目标 URL /// private const String TARGET_URL = " http://www.juiciobrennan.com/syllables/ " ; /// /// 获取单词列表 /// private const String SQL_GetWordList = " SELECT `id`, `english` FROM `all_word` WHERE `id` >= @startID AND `id`


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有