查看原文
其他

Stata: 正则表达式和文本分析

stata连享会 Stata连享会 2020-02-10

作者:游万海 (福州大学) (知乎 | 简书 | 码云)    
本期责任编辑:王俊

开始接受报名


image

给你一份公司年报,如何快速地从中找出与数值有关的内容。好朋友让你推荐好看的电影,如何快速地从豆瓣网站下载到每部电影的评分。 这里涉及到的问题就是如何从文本数据中挖掘出所需要的信息。Stata 中的字符函数为这一操作的实现提供了便利,详细可以通过 help string_functions 查看具体的用法和实例。本文主要是针对字符函数里面的正则表达式函数 ( regular expression )。

Stata 14 之前的版本主要的正则表达式函数有:regexmregexrregexsreg 代表regular,ex代表expression。匹配主要是基于 Henry Spencer's NFA 算法, 与 POSIX.2 标准相似。

     regexm : m 代表 match-----匹配。

     regexr :  r 代表 replace-----替代。

     regexs :  s 代表 subexpression-----截取。

Stata14 之前版本只能处理普通的 ASCII 字符,例如字母( a-z,A-z),数字 ( 0-9 )及普通的标点符号字符。Stata 14 之后的版本加强了编码转换 ( unicode ),能够处理其他非普通编码 ASCII 字符,如中文,日语和韩语等。相应的,Stata 14 引入了几个新的有关正则表达式命令:

Stata 14 版本主要的正则表达式函数有:ustrregexmustrregexrfustrregexraustrregexsunstr 代表 unicode string

     ustrregexm : 匹配

     ustrregexrf : f 代表 first。表示只替代第一次出现的匹配字符。

     ustrregexra : a 代表 all。表示替代全部匹配到的字符。

     ustrregexs : 截取

因此,Stata 14 加强了正则表达式的功能,可以根据 序列POSIX 字符类 等进行匹配。下面我们将进行一一说明。

一. 命令基本语法

本文以Stata 14 之前版本的三个主要正则表达式命令为例,对其语法进行说明,如下图所示:

image

从上面语法可以看出,正确使用这些命令的一个关键点在于如何填写待匹配规则,比如想从文本中匹配出与数值有关的内容,可以用 0-9,那么命令可以写为:

  1. clear

  2. input str10 income

  3. "abc"

  4. "ab"

  5. "aa"

  6. "abcd"

  7. "aad"

  8. "aab123"

  9. "cdf12345"

  10. "123"

  11. "Abc"

  12. end


  13. . gen index1 = regexm(income,"[0-9]") /* [0-9] 表示数值*/

基于此,本文以下部分主要针对匹配内容的相关规定进行阐述,分别介绍 元字符序列字符类数量词POSIX字符类等进行介绍。

二. 基本规则

(1)元字符

元字符是指的一类特殊字符,包括 * + ? ^ $ | ( ) [ ] { } \ 等。匹配这些字符需要在前面加上\

例如:

  1. clear

  2. input str3 num str2 name str10 per str6 income

  3.           -1       a          "10%"    "[9747"

  4.            1       b          "62%"    "1,234"

  5.            1       a          "53%"    "938.9"

  6.           -1       c          "48,6%"  "*8344"

  7.            2       d          "58%"    "2398"

  8.           -2       e          "46%"    "-"

  9.           -3       c          "78%"    "53822"

  10.            3       d          "92,2%"  "na"

  11.           -1       e          "65%"    "$28477"

  12.            1       b          "3,6%"   "n/a"

  13. end

若想匹配出包括 [*$ 等特殊字符的部分,可以利用如下代码:

  1. gen index  = regexm(income,"\[")   /*匹配包含[号的*/

  2. gen index1 = regexm(income,"\\$")  /*匹配包含$号的*/

  3. gen index2 = regexm(income,"[\$]")

  4. gen index3 = regexm(income,"[$]")

  5. gen index4 = regexm(income,"[`=char(36)']") /*利用charlist查看相应的代码*/

  6. gen index5 = regexm(income,"\*")            /*匹配包含*号的*/

  7. gen index6 = regexm(income,"[\*|\[]")       /*|表示或者,匹配包含[号或者*号的*/

  8. list

需要特别注意的是 $ 符号比较特殊,其在 Stata 中还可以表示全局宏的引用,所以可以不加 \

(2) 序列

较为常用的序列主要有如下:

    \d 匹配数字字符

    \D 匹配非数字字符

    \s 匹配间隔符(空格)

    \S 匹配非间隔符(非空格)

    \w 匹配单词字符

    \W 匹配非单词字符

    \b 匹配词界

    \B 匹配非词界

第一: `\D` 表示非数值,`\d` 表示数值

回顾下前面的例子,若要从文本中匹配包括数值的部分,可以用 0-9,这里也可以用 \d 进行匹配。例如:

  1. clear

  2. input str12 income

  3. "123"

  4. "acb"

  5. "12a"

  6. end


  7. gen index1 = ustrregexm(income,"\D") /*\D表示非数值*/

  8. gen index2 = ustrregexm(income,"\d") /*\D表示数值*/

第二:`\w` 和 `\W` 表示单词和非单词字符。

单词字符是包括下划线的任何单词字符(字母,数字,下划线,汉字),即 [a-zA-Z_0-9]

  1. clear

  2. input str64 income

  3. "the dandelion war 2010"

  4. end

  5. gen make2 = income

  6. gen make3 = income

  7. replace make2 = ustrregexra(make2, "\w", "_")

  8. replace make3 = ustrregexra(make2, "\W", "_")

第三:`\b` 和 `\B` 是位置匹配符

如果前面和后面的字符不全是 \w (字母,数字,下划线,汉字),则匹配;反过来理解就是,如果 \b 的前面和后面都是 \w,则不匹配。
前面讲的匹配都是匹配内容,而这里是匹配位置。这组命令也是非常常用,请看如下例子。

  1. clear

  2. input str12 income

  3. "abc"

  4. "abc"

  5. "1ab"

  6. "1abc"

  7. "ab_"

  8. "ab"

  9. end

上述数据中都包括了 ab 字符,若我们只想匹配最后一个,看如下命令是否可以:

  1. gen index1 = ustrregexm(income,"ab") /*等价于gen index1 = regexm(income,"ab") */

  1. list


  2.     +-----------------+

  3.     | income   index1 |

  4.     |-----------------|

  5.  1. |    abc        1 |

  6.  2. |    abc        1 |

  7.  3. |    1ab        1 |

  8.  4. |   1abc        1 |

  9.  5. |    ab_        1 |

  10.     |-----------------|

  11.  6. |     ab        1 |

  12.     +-----------------+

从匹配结果来看,把所有包括 ab 的元素都匹配出来的,这是因为遵循了贪婪匹配( greedy )模式。在这种情况下,我们可以使用位置匹配符进行限定。

  1. gen index2 = ustrregexm(income,"\bab")

  2. // Q: 也等价于 gen index2 = regexm(income,"\bab") 吗?

  3. gen index3 = ustrregexm(income,"ab\b")

  4. gen index4 = ustrregexm(income,"\bab\b")

  1. list


  2.     +--------------------------------------------+

  3.     | income   index1   index2   index3   index4 |

  4.     |--------------------------------------------|

  5.  1. |    abc        1        1        0        0 |

  6.  2. |    abc        1        1        0        0 |

  7.  3. |    1ab        1        0        1        0 |

  8.  4. |   1abc        1        0        0        0 |

  9.  5. |    ab_        1        1        0        0 |

  10.     |--------------------------------------------|

  11.  6. |     ab        1        1        1        1 |

  12.     +--------------------------------------------+

哇,大家可以看到,index4 就是我们要的内容。这就是位置匹配符的神奇之处!此外,位置匹配符还包括 ^$,匹配开始和结束位置。
例如:

  1. clear

  2. input str10 income

  3. "abc"

  4. "ab"

  5. "aa"

  6. "abcd"

  7. "aad"

  8. "a1"

  9. "aab123"

  10. "cdf12345"

  11. "123"

  12. end

  13. gen index1 = regexm(income, "(^[a-z]+)([0-9]$)") /*^放在括号外表示以...开始;$表示结束*/

  1. . list


  2.     +-------------------+

  3.     |   income   index1 |

  4.     |-------------------|

  5.  1. |      abc        0 |

  6.  2. |       ab        0 |

  7.  3. |       aa        0 |

  8.  4. |     abcd        0 |

  9.  5. |      aad        0 |

  10.     |-------------------|

  11.  6. |       a1        1 |

  12.  7. |   aab123        0 |

  13.  8. | cdf12345        0 |

  14.  9. |      123        0 |

  15.     +-------------------+

(3) 字符类

字符类主要包括如下:

    [aeiou] 匹配任意元音字母

    [AEIOU] 匹配任何一个大写元音

    [0123456789] 匹配任意单个数字

    [0-9] 匹配任意数字(同上)

    [a-z] 匹配任何ASCII小写字母

    [A-Z] 匹配任何ASCII大写字母

    [a-zA-Z0-9] 匹配任意上面的类

    [^aeiou] 匹配除小写元音外的字母

    [^0-9] 匹配除数字外的字符

  1. clear

  2. input str10 income

  3. "abc"

  4. "ab"

  5. "aa"

  6. "abcd"

  7. "aad"

  8. "aab123"

  9. "cdf12345"

  10. "123"

  11. "Abc"

  12. end

  13. gen index1 = ustrregexm(income,"[0-9]") /* [0-9] 表示数值,是否可以用\d?*/

  14. gen index2 = ustrregexm(income,"[a-z]") /* [a-z] 表示小写字母*/

  15. gen index3 = ustrregexm(income,"[aeiou]") /* aeiou 表示元音 */

  16. gen index4 = ustrregexm(income,"<a href="#footnote-aeiou"><sup>[aeiou]</sup></a>") /*括号[]内时,^表示否定,即排除aeiou*/

  17. gen index5 = ustrregexm(income,"[A-Z]") /* [A-Z] 表示大写字母*/

(4) 表示数量

表示数量有两类:

第一类

    {n} 前面待匹配的项目将匹配n个;

    {n,} 前面待匹配的项目将匹配n个或更多个;

    {n,m} 前面待匹配的项目将匹配至少n个最多m个;

第二类

    ? 前面的待匹配的项目是可选的,且最多匹配一个

    * 前面待匹配的项目可以匹配0个或更多个

    + 前面待匹配的项目将匹配一个或多个

  1. clear

  2. input str10 income

  3. "abc"

  4. "ab"

  5. "aa"

  6. "abcd"

  7. "aad"

  8. "a1"

  9. "aab123"

  10. "cdf12345"

  11. "123"

  12. end


  13. gen index1 = ustrregexm(income, "[a]{1}") /*{1}表示数量,匹配a,{1}表示1次;这里是greedy匹配,只要出现>=1次就匹配*/

  14. gen index2 = ustrregexm(income, "[a]{2}")

  15. gen index3 = ustrregexm(income, "[0-9]{2}")

  16. gen index4 = ustrregexm(income, "[0-9]{3}")

  17. gen index5 = ustrregexm(income, "[0-9]{4}")

  18. gen index6 = ustrregexm(income, "[0-9]{1,3}")

  19. gen index7 = ustrregexm(income, "[0-9]{4,5}")

  20. gen index8 = ustrregexm(income, "[0-9]+") /*+表示1次或多次*/

  21. gen index9 = ustrregexm(income, "[0-9]*") /*表示0次或多次*/

  22. gen index10 = ustrregexm(income, "[0-9]?") /*表示0次或1次*/

  23. gen index11 = ustrregexm(income, "^[0-9]") /*^放在括号外表示以...开始*/

  24. gen index12 = ustrregexm(income, "(^[a-z]+)[1]$") /*若只想匹配 **a1**原始,以字母开头,以数字结束*/

  25. gen index13 = ustrregexm(income, "(^[a-z]+)[0-9]$") /*效果等同于上句命令*/

(5) POSIX字符类

POSIX字符类是用 "[[ ]]“ 括起来的正则表达,常见的 POSIX 字符类有:

    [[:lower:]] 小写字母

    [[:upper:]] 大写字母

    [[:alpha:]] 所有字母 ([[:lower:]] and [[:upper:]])

    [[:digit:]] 数字: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

    [[:alnum:]] 字母和数字 ([[:alpha:]] and [[:digit:]])

    [[:blank:]] 空白字符: space and tab

    [[:cntrl:]] 控制字符

    [[:punct:]] 标点符号: ! ” # % & ' ( ) * + , - . / : ;

    [[:space:]] 空格字符:制表符,换行符, 垂直制表符,换页符,回车和空格

    [[:xdigit:]] 十六进制数字: 0-9 A B C D E F a b c d e f

    [[:print:]] 控制字符 ([[:alpha:]], [[:punct:]] and space)

    [[:graph:]] 图形化字符 ([[:alpha:]] and [[:punct:]])

  1. clear

  2. input str10 income

  3. "abc"

  4. "aB"

  5. "aa"

  6. "abcd"

  7. "Aad"

  8. "a1"

  9. "aab123"

  10. "cdf12345"

  11. "123"

  12. end

  13. gen index1 = ustrregexm(income, "[[:lower:]]") /*小写字母*/

  14. gen index2 = ustrregexm(income, "[[:upper:]]") /*大写字母*/

  15. gen index3 = ustrregexm(income, "[[:digit:]]") /*大写字母*/

(6) 回溯引用

我们先看这个例子(例子数据来源于 statalist 论坛):

  1. clear

  2. input strL report_text

  3. "indication I want this 1 view"

  4. "indication I want this 2 views"

  5. "indications I want this 3 view"

  6. "indications I want this 4 views"

  7. "history I want this 5 view"

  8. "history I want this 6 views"

  9. "xxx I dont want this yyy"

  10. "indication I dont want this either yyy"

  11. "xxx nor this view"

  12. end

若我们想取出 I want this 1I want this 2等,大家发现与前面说的匹配存在什么不同?前述例子我们都是从多个数中匹配出符合条件的数,而这里是从每个数中取出符合条件的一部分。这时候可以采用回溯引用方法。即先根据字符数据,利用正则表达式将完整的字符匹配出来,然后利用()来取出我们感兴趣的部分。这时可以利用 regexmregexs 函数。

0表示匹配出来的全部内容;1表示第一个括号的内容;2表示第二个括号的内容;3表示第三个括号的内容。例如:

  1. gen indication0=regexs(0) if regexm(lower(report_text),"^(indications|indication|history)(.*)(views|view)$") /* .表示匹配任何字符*/

  2. gen indication1=regexs(1) if regexm(lower(report_text),"^(indications|indication|history)(.*)(views|view)$")

  3. gen indication2=regexs(2) if regexm(lower(report_text),"^(indications|indication|history)(.*)(views|view)$")

  4. gen indication3=regexs(3) if regexm(lower(report_text),"^(indications|indication|history)(.*)(views|view)$")

  5. list

  1. . list, compress


  2.     +------------------------------------------------------------------------------------------------------------------+

  3.     |                            report_text                       indication0   indication1       indication2   ind~3 |

  4.     |------------------------------------------------------------------------------------------------------------------|

  5.  1. |          indication I want this 1 view     indication i want this 1 view    indication    i want this 1     view |

  6.  2. |         indication I want this 2 views    indication i want this 2 views    indication    i want this 2    views |

  7.  3. |         indications I want this 3 view    indications i want this 3 view   indications    i want this 3     view |

  8.  4. |        indications I want this 4 views   indications i want this 4 views   indications    i want this 4    views |

  9.  5. |             history I want this 5 view        history i want this 5 view       history    i want this 5     view |

  10.     |------------------------------------------------------------------------------------------------------------------|

  11.  6. |            history I want this 6 views       history i want this 6 views       history    i want this 6    views |

  12.  7. |               xxx I dont want this yyy                                                                           |

  13.  8. | indication I dont want this either yyy                                                                           |

  14.  9. |                      xxx nor this view                                                                           |

  15.     +------------------------------------------------------------------------------------------------------------------+


  16. .

思考

  1. clear

  2. input str10 income

  3. "abc123"

  4. "a1"

  5. "abcdef12345"

  6. end

如何取出其中的数值部分?

(7) ()和[]的区别

  1. clear

  2. input str10 income

  3. "abc"

  4. "ab"

  5. "aa"

  6. "abcd"

  7. "aad"

  8. "aab123"

  9. "cdf12345"

  10. "123"

  11. end

  12. gen index1 = regexm(income,"[ab]") /*表示匹配a或者b*/

  13. gen index2 = regexm(income,"(a|b)") /*表示匹配a或者b*/

  14. gen index3 = regexm(income,"(ab)") /*表示匹配ab*/

(8) 常用匹配

(参考来源:https://blog.csdn.net/wangjia55/article/details/7877915)

  • 匹配中文字符的正则表达式: [\u4e00-\u9fa5]

评注:匹配中文还真是个头疼的事,有了这个表达式就好办了

  • 匹配双字节字符(包括汉字在内):[^\x00-\xff]

评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)

  • 匹配空白行的正则表达式:\n\s*\r

评注:可以用来删除空白行

  • 匹配 HTML 标记的正则表达式:<(\S?)[^>]>.?|<.? /&gt;

评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力

  • 匹配首尾空白字符的正则表达式:^\s|\s$

评注:可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等)

  • 匹配 Email 地址的正则表达式:\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*

评注:表单验证时很实用

  • 匹配网址 URL 的正则表达式:[a-zA-z]+://[^\s]*

评注:网上流传的版本功能很有限,上面这个基本可以满足需求

  • 匹配帐号是否合法(字母开头,允许 5-16 字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

评注:表单验证时很实用

  • 匹配国内电话号码:\d{3}-\d{8}|\d{4}-\d{7}

评注:匹配形式如 0511-4405222021-87888822

  • 匹配腾讯 QQ 号:[1-9][0-9]{4,}

评注:腾讯 QQ 号从 10000 开始

  • 匹配中国邮政编码:[1-9]\d{5}(?!\d)

评注:中国邮政编码为 6 位数字

  • 匹配身份证:\d{15}|\d{18}

评注:中国的身份证为 15 位或 18 位

  • 匹配 ip 地址:\d+.\d+.\d+.\d+

评注:提取 ip 地址时有用

三. Stata 范例:利用正则表达式爬取豆瓣影评数据

说明:以下例子我主要是利用了外部命令 moss,大家可以用上述讲过的命令试试

  1. clear

  2. import delimited "https://movie.douban.com/top250?start=25&filter=",delimiters("^") varnames(1) rowrange(3) encoding("UTF-8") clear


  3. **评价人数 net install moss, from(http://fmwww.bc.edu/RePEc/bocode/m)

  4. moss doctypehtml, match("(人评价)") regex  prefix(c_)

  5. gen comment_num = real(regexs(1)) if regexm(doctypehtml,"([0-9]+)") & c_count==1


  6. tempfile comment_num score title year comment

  7. preserve

  8. drop if comment_num==.

  9. keep comment_num

  10. save `comment_num',replace

  11. restore

  12. **use `comment_num',clear


  13. **评分

  14. moss doctypehtml, match("(v:average)") regex prefix(s_)

  15. gen score = real(regexs(1)) if regexm(doctypehtml,"([0-9][\.][0-9])") & s_count==1

  16. preserve

  17. drop if score==.

  18. keep score

  19. save `score',replace

  20. restore

  21. **use `score',clear


  22. **标题

  23. gen title1 = regexs(1) if regexm(doctypehtml,"(\<span(.+)title(.*)\>$)")==1

  24. **gen title6 = regexs(1) if regexm(doctypehtml,"(\<)span(.+)(.*)title(\>)(^&nbsp)(.+)")==1

  25. gen title2 = title1 if regexm(title1,"(\&nbsp)")==1

  26. gen title=(title1~=title2)

  27. gen title_for = doctypehtml if title==1

  28. preserve

  29. drop if title_for==""

  30. drop in 1

  31. keep title_for

  32. split title_for,parse(> <)

  33. keep title_for3

  34. save `title',replace

  35. restore


  36. **年份

  37. gen year= real(regexs(1)) if regexm(doctypehtml,"([0-9][0-9][0-9][0-9])(\&nbsp)")

  38. preserve

  39. drop if year==.

  40. keep year

  41. save `year',replace

  42. restore


  43. **精选评论

  44. gen comment_text= regexs(0) if regexm(doctypehtml,"(\<)span(.+)inq")

  45. gen comment = doctypehtml if comment_text~=""

  46. preserve

  47. drop if comment==""

  48. keep comment

  49. split comment,parse(> 。)

  50. keep comment2

  51. save `comment',replace

  52. restore


  53. use `comment_num',clear

  54. merge 1:1 _n using `year',nogenerate

  55. merge 1:1 _n using `comment',nogenerate

  56. merge 1:1 _n using `title',nogenerate

  57. merge 1:1 _n using `score',nogenerate


  58. **split comment,parse(> 。)

  59. drop if year==.

  60. list,table

  61. order title_for3 year score comment_num comment2

  62. rename (title_for3 year score comment_num comment2) (电影名称 出版年份 电影评分 评论人数 经典评论)

  1. --------------------------------------------------------------------------------------------------------------------------

  2.               电影名称   出版年份   电影评分    评论人数                                                  经典评论

  3.     |---------------------------------------------------------------------------------------------------------------------

  4.  1. |           乱世佳人    1939     9.2   338897                                            Tomorrow is another day

  5.  2. |    蝙蝠侠:黑暗骑士    2008     9.1   445292                                                           无尽的黑暗

  6.  3. |               活着    1994     9.2   367641                                                    张艺谋最好的电影

  7.  4. |          天堂电影院    1988    9.1   371147                那些吻戏,那些青春,都在影院的黑暗里被泪水冲刷得无比清晰

  8.  5. |    少年派的奇幻漂流    2012     9   731586                                           瑰丽壮观、无人能及的冒险之旅

  9.     |---------------------------------------------------------------------------------------------------------------------

  10.  6. |           十二怒汉    1957     9.4   218126                                                     1957年的理想主义

  11.  7. |           鬼子来了    2000     9.2   303688                                           对敌人的仁慈,就是对自己残忍

  12.  8. |  指环王3:王者无敌    2003     9.1   363564                                                            史诗的终章

  13.  9. |           控方证人    1957     9.6   149201                                                     比利·怀德满分作品

  14. 10. |           天空之城    1986       9   423985                                                 对天空的追逐,永不停止

  15.     |---------------------------------------------------------------------------------------------------------------------

  16. 11. |         搏击俱乐部    1999       9   478422                        邪恶与平庸蛰伏于同一个母体,在特定的时间互相对峙

  17. 12. |         飞屋环游记    2009     8.9   645980                                 最后那些最无聊的事情,才是最值得怀念的

  18. 13. |  大话西游之月光宝盒    1995     8.9   544247                                                             旷古烁今

  19. 14. |           罗马假日    1953       9   488403                                                     爱情哪怕只有一天

  20. 15. |       摔跤吧!爸爸    2016     9.1   661737    你不是在为你一个人战斗,你要让千千万万的女性看到女生并不是只能相夫教子

  21.     |---------------------------------------------------------------------------------------------------------------------

  22. 16. |           窃听风暴    2006     9.1   289688                                                            别样人生

  23. 17. |     哈尔的移动城堡    2004     8.9   461128                                                带着心爱的人在天空飞翔

  24. 18. |             辩护人    2013     9.2   287409                                            电影的现实意义大过电影本身

  25. 19. |         闻香识女人    1992       9   414805                                                       史上最美的探戈

  26. 20. |         两杆大烟枪    1998     9.1   327431                               4个臭皮匠顶个诸葛亮,盖·里奇果然不是盖的

  27.     |---------------------------------------------------------------------------------------------------------------------

  28. 21. |         飞越疯人院    1975       9   331838                                                            自由万岁

  29. 22. |           死亡诗社    1989       9   355404   当一个死水般的体制内出现一个活跃的变数时,所有的腐臭都站在了光明的对面

  30. 23. |          V字仇杀队    2005     8.8   538168                                             一张面具背后的理想与革命

  31. 24. |             海豚湾    2009     9.3   218285                                     海豚的微笑,是世界上最高明的伪装

  32. 25. |              教父2    1974     9.1   242211                                                         优雅的孤独

  33.     +---------------------------------------------------------------------------------------------------------------------

思考:上述程序只爬取了第一页的数据,若要爬取前10页的数据(^^这里仅做科研目的,大家悠着点,不要爬虫的太频繁),应该如何?

  1. tempfile building

  2. save `building', emptyok


  3. scalar web1="https://movie.douban.com/top250?start="

  4. scalar web3="&filter="

  5. forv i = 10(-1)1{

  6. local k = (`i'-1)*25

  7. local url=web1 + "`k'" + web3

  8. *scalar list url

  9. di `"`url'"'

  10. import delimited  "`url'",delimiters("^") varnames(1) rowrange(3) encoding("UTF-8") clear


  11. **评价人数 net install moss, from(http://fmwww.bc.edu/RePEc/bocode/m)

  12. moss doctypehtml, match("(人评价)") regex  prefix(c_)

  13. gen comment_num = real(regexs(1)) if regexm(doctypehtml,"([0-9]+)") & c_count==1


  14. tempfile comment_num score title year comment

  15. preserve

  16. drop if comment_num==.

  17. keep comment_num

  18. save `comment_num',replace

  19. restore


  20. **评分

  21. moss doctypehtml, match("(v:average)") regex prefix(s_)

  22. gen score = real(regexs(1)) if regexm(doctypehtml,"([0-9][\.][0-9])") & s_count==1

  23. preserve

  24. drop if score==.

  25. keep score

  26. save `score',replace

  27. restore



  28. **标题

  29. gen title1 = regexs(1) if regexm(doctypehtml,"(\<span(.+)title(.*)\>$)")==1

  30. **gen title6 = regexs(1) if regexm(doctypehtml,"(\<)span(.+)(.*)title(\>)(^&nbsp)(.+)")==1

  31. gen title2 = title1 if regexm(title1,"(\&nbsp)")==1

  32. gen title=(title1~=title2)

  33. gen title_for = doctypehtml if title==1

  34. preserve

  35. drop if title_for==""

  36. drop in 1

  37. keep title_for

  38. split title_for,parse(> <)

  39. keep title_for3

  40. save `title',replace

  41. restore



  42. **年份

  43. gen year= real(regexs(1)) if regexm(doctypehtml,"([0-9][0-9][0-9][0-9])(\&nbsp)")

  44. preserve

  45. drop if year==.

  46. keep year

  47. save `year',replace

  48. restore


  49. **精选评论

  50. gen comment_text= regexs(0) if regexm(doctypehtml,"(\<)span(.+)inq")

  51. gen comment = doctypehtml if comment_text~=""

  52. preserve

  53. drop if comment==""

  54. keep comment

  55. split comment,parse(> 。)

  56. keep comment2

  57. save `comment',replace

  58. restore


  59. use `comment_num',clear

  60. merge 1:1 _n using `year',nogenerate

  61. merge 1:1 _n using `comment',nogenerate

  62. merge 1:1 _n using `title',nogenerate

  63. merge 1:1 _n using `score',nogenerate

  64. drop if year==.

  65. append using `building'

  66. save `"`building'"', replace

  67. }

  68. list in 1/10

  69. use `"`building'"',clear

四、总结

  1. 若用 Stata 14以后的版本,尽量用 ustr 开头的命令,功能较为齐全。

  2. 学习正则表达式要多练习,多练习,多练习!!


参考资料

  1. How can I extract a portion of a string variable using regular expressions?

  2. What are regular expressions and how can I use them in Stata?

  3. New program for regular expressions

  4. Stata14 VS Stata13 之字符串函数 PK

  5. Regular Expressions in Stata

  6. Regular Expression Matching Can Be Simple And Fast

  7. 在线正则表达式测试


附录:推文 dofile 合集

【Stata: 正则表达式和文本分析】推文中的所有 dofile 文档


关于我们

  • Stata 连享会(公众号:StataChina)】由中山大学连玉君老师团队创办,旨在定期与大家分享 Stata 应用的各种经验和技巧。

  • 公众号推文同步发布于 CSDN-Stata连享会 、简书-Stata连享会 和 知乎-连玉君Stata专栏。可以在上述网站中搜索关键词StataStata连享会后关注我们。

  • 点击推文底部【阅读原文】可以查看推文中的链接并下载相关资料。

  • Stata连享会 精彩推文1  || 精彩推文2

联系我们

  • 欢迎赐稿: 欢迎将您的文章或笔记投稿至Stata连享会(公众号: StataChina),我们会保留您的署名;录用稿件达五篇以上,即可免费获得 Stata 现场培训 (初级或高级选其一) 资格。

  • 意见和资料: 欢迎您的宝贵意见,您也可以来信索取推文中提及的程序和数据。

  • 招募英才: 欢迎加入我们的团队,一起学习 Stata。合作编辑或撰写稿件五篇以上,即可免费获得 Stata 现场培训 (初级或高级选其一) 资格。

  • 联系邮件: StataChina@163.com

往期精彩推文

欢迎加入Stata连享会(公众号: StataChina)


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存