当前位置:网站首页 >> 网络

嘀正則表達式快速上手指南

时间:2020-01-17 01:09:22 来源:互联网 阅读:0次

嘀~正则表达式快速上手指南

按:本文为雷锋字幕组编译的技术博客,原标题Regular Expressions for Data Scientists,来源dataquest

翻译 | 汪其香 Noddleleslee 陈亚彬 赵朋飞 杨婉迪 校对|余杭 整理| 凡江

作为数据科学家,快速处理海量数据是他们的必备技能有时候,这包括大量的文本语料库例如,假设要找出在 Panama Papers 泄密事件中邮件的发送方和接收方,我们需要详细筛查1150万封文档我们可以手工完成上述任务,人工阅读每一封邮件,读取每一份发给我们的邮件,或者我们可以借助Python的力量毕竟,代码存在的一个至关重要的理由就是自动处理任务

尽管如此,从头开始编写脚本、写脚本、抓取数据需要大量的时间和精力这正是正则表达式的用武之地RE,regex 和regular patterns 表达的意思皆是正则表达式,它形成一门简洁的语言帮助我们快速地整理和分析文本

正则出现在1956年,Stephen Cole Kleene 创建它用于描述人类神经系统的MP模型(McCulloch and Pitts model)的概念1960年代,Ken Thompson 将这个概念添加到类似Windows记事本的文本器中,自此正则开始壮大

正则一个关键特性是节省脚本我们可以视其为代码的捷径没有它,我们不得不为同样目的敲大量的垃圾代码

本教程需要Python基础知识如果你理解if-else 表达式,while 语句和for 循环,列表和字典,本教程的大部分都可以搞定啦此外你需要代码器,如Visual Studio Code,PyCharm 或Atom都可以这样当我们遍历每一行代码时就不会茫然,此外基础的pandas库也是必要的如果你需要复习,可以跳转到pandas的教程

学完本教程,你会对正则的使用熟悉很多,可以使用re模块的基础模式和函数完成字符串分析我们也学会如何高效地使用正则和pandas库化大量紊乱的数据集为有序

现在,让我们看看正则可以做些什么

数据集介绍

我们使用Kaggle的欺诈邮件文本语料库它包括1998到2007发出的上千封钓鱼邮件点击此处可以下载数据集在对整个语料库操作之前,让我们先学习在一封邮件应用正则表达

Python正则表达式模块的介绍

首先打开文本文件读取数据,设置为只读模式,并读取数据集,将上述操作结果赋给变量 fh(“file handle” 即文件句柄)

请注意我们在设置目录路径之前添加r它将转换字符串为原始字符串,避免机器读取字符时候引起冲突,例如Windows的目录路径中的反斜杠

你也许注意到我们现在并没有使用整个语料库相反地,我们先人工挑选语料库的相对靠前的一些邮件作为测试文件本教程不打算每次都展示上千行的结果,每次都打印其中的一部分作为测试这可能会让人感到恼怒你可以使用整个语料库,也可以使用我们的测试文件无论哪种方式,都能很好得获得学习经验

现在,假设我们现在想知道邮件的来源我们可以在自己的Python尝试如下代码:

或者,我们可以使用正则表达式:

我们来遍历这段代码首先导入 re 模块然后敲出图示余下代代码这个例子中,这比原来的Python 代码仅少 1 行 然而随着脚本行数的快速增长,正则表达式可以节省脚本的代码量

ndall() 以列表形式返回字符串中符合模式的所有实例它是Python内置 re 模块中经常使用的函数让我们来剖析 ndallndall(pattern, string)接受两个参数pattern表示我们想要搜索的子字符串,string 表示我们想要搜索的主字符串主字符串可以由多行组成

.* 是字符串模式的简写我们很快就会解释它的细节现在它们与From: 域中的名称和电子邮件地址相匹配

在让我们更深一步探索之前,先浏览一下常用的正则表达式

常用的正则表达式

我们之前用到的ndall() 包含From:的字符串这个函数当我们明确知道搜索目标时候十分有用,甚至包括明确字母拼写和是否大小写如果我们不明确知道搜索目标时,该函数就会失效幸运的是正则表达有解决这个问题的基本模式让我们看一些这篇文章将用到的:

\w 匹配字母数字字符,即a-z,A-Z,它也匹配下划线和波折号

\d 即

\s matches 匹配空白格,包括制表符、换行字符、回车符和空格字符

\S 匹配非空白格字符

. 匹配除换行字符\n外的任意字符串

有这些正则表达式的说明在手,你就可以在我们解释上述代码时能够快速地理解

使用正则表达式

现在我们来解释ndall(From:.*, text) 中.* 的作用首先看. :

From:后面添加. ,表示寻找它旁边的字符,因为.查找 \n外的任何字符,它也会捕捉肉眼不可见的空格我们可以添加更多的点来验证

看起来添加很多点可以获得行中我们想要的剩余部分但这是冗余的而且我们不知道要敲多少个点这就是很有用的*的由来

* 匹配其左侧表达式的0个或多个模式的实例这意味它寻找重复模式当我们寻找重复模式时,称为贪婪搜索否则,我们称之为非贪婪搜索或懒惰搜索

让我们用* 构建一个对 . 的贪婪搜索

因为 * 匹配其左侧 0 个或多个模式类的实例,而 . 在其左侧,因此我们可以获得From: 到行末的所有字符这种漂亮高效的方式可以输出完整的行

我们甚至可以更进一步,只分离出名字:

我们使用ndall() 返回包含From:.* 模式的列表,就像我们以前做的那样为了简洁起见 我们给match 变量赋以上述操作的结果接下来,我们迭代列表每一次循环,我们都再次执行ndall 这一次,这个函数从个引号开始匹配

请注意我们在个引号旁使用反斜杠反斜杠是用于转义其他特殊字符的特殊字符例如,当我们想使用引号作为字符串而不是特殊字符时,我们用反斜杠来表示转义:\如果不使用反斜杠表示转义,就是.*,Python解释器视作两个空字符串之间读取一个句点和一个星号这就会出现错误,脚本不能运行因此,关键是使用反斜杠表示转义

在个引号匹配之后,.* 获取行中直到下一个转义的引号的所有字符获取引号内的名字每个名字都在方括号内打印出,因为ndall 以列表形式返回匹配内容如果我们需要获取电子邮件地址呢

看起来很简单不是嘛只是匹配模式有些许不同,让我们逐一攻破

以下是如何匹配电子邮件地址的前面部分:

电子邮件总是包含@符号,让我们从它开始电子邮件@符号之前的部分可能包含字母数字字符,\w 就派上用场然而,因为一些邮件包含句点或破折号,这是不够的我们用\S 来查找非空白字符但\w\S 仅仅找到两个字符添加 * 重复寻找过程因此模式前半部分是:\w\S*@

现在来看看@符号后半部分的模式:

域名通常包含字母数字字符、句点和破折号这很简单,一个 . 就能搞定为了使用贪婪模式,我们用*来扩展搜索这使我们可以匹配直到行结束的任何字符

如果我们仔细观察这行,我们会发现每个电子邮件都封装在尖括号内,和 我们的模式.*包括闭合的尖括号让我们纠正一下:

电子邮件地址以字母数字字符结束,所以我们用\w模式覆盖因此@ 符号后面是.*\w,这意味着我们想要的模式是一组以字母数字字符结尾的字符这不包括

完整电子邮件地址模式是:\w\S*@.*\w

这是相当多的工作熟练使用正则表达式需要一段时间,但是一旦您掌握它的模式,您就能够更快地为字符串分析编写代码接下来,我们将运行一些re 模块常见函数,当我们开始重新整理语料库时它们将非常有用

常见的正则表达式函数

ndall() 无疑是有用的,re 模块提供了更多同样便捷的函数

包括:

arch()

lit()

b()

在使用它们把杂乱无序的语料库变为有序之前,我们对它们逐一分析

arch()

ndall() 以列表形式返回匹配字符串中满足模式的所有实例,arch() 匹配字符串中模式的个实例,并将其作为一个re 模块的匹配对象

和 ndall() 类似, arch() 也接受两个参数个参数是匹配的模式,第二个参数是要搜索的字符串范围这里为了简洁起见,我们已经将结果赋值给match 变量

因为 arch() 返回一个re 模块的匹配对象,我们不能直接打印出对应的名字和电子邮件地址 相反,我们必须先采用 group()这个函数. 我们已经在上面的代码中打印了它们类型,可以看出group() 将匹配对象转化成一个字符串

我们也可以看到打印match 时显示的是对应的属性而不是字符串本身, 而打印 oup() 只显示字符串

lit()

假设我们需要一种快速的方法来获取电子邮件地址的域名我们可以用三次正则操作,像这样:

行用法前面已经提到了我们返回一个字符串列表,每个字符串包含From: 字段的内容,并将其赋给变量接下来的通过遍历这个列表来查找邮件的地址同时通过迭代电子邮件地址和使用 re 模块的split() 函数来把每一个地址剪成两半,用 @作为分隔符再打印出来

b()

另一个方便的 re 函数是 b()正如函数名所示,它用来替换字符串的各个部分举个例子:

前两行已经在前面出现过了

在第三行我们将 address 作为 b() 函数的第三个参数,即邮件标题中完整的From: 字段

b() 需要三个参数个是被代替的子字符串,第二是想要放在目标位置的字符串,而第三是主字符串

pandas 中的正则表达式

现在我们有了正则表达式的一些基础知识,我们可以尝试一些更复杂的然而,我们需要正则表达式跟pandas Python数据分析库结合Pandas 库中有一个很有用的把数据组织成整齐表格的对象,即 DataFrame 对象,也可以从不同的角度理解它结合正则表达式的代码,它就像用一个特别锋利的刀雕刻软黄油

不用担心从来没用过 Pandas我们会通过代码一步一步进行,这样你就不会感到困惑正如我们在引言中提到的,如果你想详细学习,请访问Pandas tutotial

我们可以通过 Anaconda 或者 pip 来下载 pandas 库 详情请查看安装指南

用正则表达式和Pandas分拣邮件

Corpus 是一个包含数千封电子邮件的文本文件我们将使用正则表达式和Pandas 来将每封电子邮件适当分类 使Corpus 语料库更便于阅读和分析我们会将每封邮件分为以下几个类别之一:

sender_name

sender_address

recipient_address

recipient_name

date_sent

subject

email_body

每个类别将成为我们Pandas数据帧或表格中的一列这非常有用,因为我们可以自行处理每一列例如,我们可以直接编写来找出电子邮件来自哪个域名,而不需要首先编码来将电子邮件地址与其他部分隔离开来基本上,对数据集先分类可以让我们编写更简洁的代码反过来,简洁的代码减少了机器所需的操作数量,这加快了我们的处理速度,特别是在处理大量数据集时

准备Script

我们从上面一个简单的脚本开始从头开始以便弄清楚它们内部运行的原理

在代码的一开始首先导入 re 和pandas 模块,我们导入的Python email 包对于邮件正文很重要,如果仅仅使用正则表达式来处理电子邮件的正文会相当复杂,可能需要足够的清理不必要信息方面的工作才能保证它能正常运行

email 包然后我们创建一个空的列表emails 用来存放包含每个电子邮件详细信息的字典

我们经常将代码的结果打印到屏幕上来判断代码是对还是错然而,由于数据集中有成千上万的电子邮件,打印出上千行到屏幕上会占据本教程页面我们当然不想让你一遍又一遍地滚动成千上万行的结果因此,正如我们在本教程开始时所做的,我们打开并阅读了Corpus的较短版本为了本次教程我们手工编写一点你可以使用实际的数据集

每次运行 print() 函数,你只需几秒钟就可以把几千行打印到屏幕上

现在我们开始使用正则化表达式

我们用 re 模块的 split 函数将 fh 中整个文本块拆分为一个单独的电子邮件列表,分配给 contents这很重要,因为我们希望通过循环遍历列表来一个个地处理电子邮件但是我们怎么知道用 From r来分割呢?我们之所以知道这一点,是因为在编写脚本之前查看了文件我们没有必要仔细阅读数千电子邮件只需要通过前几行来大致看看数据的结构是什么样子的正因为如此,每个电子邮件前面都是字符串 From r我们已经截图了文本文件的样子:

邮件用 “From r”开头

绿色部分是个电子邮件蓝色部分是第二个电子邮件我们可以看到,这两个电子邮件都是以 From r开头,用红色的框来显示

我们在这个教程中之所以使用 Fraudulent Email Corpus是为了表明当数据是无序的和不熟悉的时候,我们不能只依靠代码来处理,它需要一双眼睛就像刚刚展示的那样,我们需要查看 Corpus 来研究它的结构另外 这样的数据可能还需要再处理 ,这个 Corpus 语料库也是同理举个例子,即使我们用本教程的完整脚本算出本数据集包含3977 封邮件,实际上更多有些邮件的开头没有 From r字段所以没有被拆分成单独的邮件但是我们保留了这个结果以免它无穷无尽

注意我们也用了 p(0)去掉列表中的个元素那是在封电子邮件的前面有From r 字符串当这个字段被分割的时候,在索引0的位置生成了一个空字符串我们即将编写的脚本是为电子邮件而设计的如果出现空字符串它可能会报错去掉空字符串可以让我们避免这些错误打断脚本的运行

以循环方式获取每个名称和地址

接下来我们在电子邮件的 contents 列表中工作

上面的代码中用 for 循环去遍历 contents 这样我们就可以一个一个处理每封邮件我们创建一个字典, emails_dict,这将保存每个电子邮件的所有细节,如发件人的地址和姓名事实上,这些是我们要寻找的项信息

这个过程总共有 3 步,首先是找到 From: 字段

步,我们通过 arch() 函数找到完整的 From: 字段句点 .表示除了\n之外的任何字符 ,* 延伸到该行的结尾处然后将它赋给变量 sender.

但是,数据并不总是直截了当的常常会有意想不到的情况出现例如,如果没有 From: 字段怎么办脚本将报错并中断在步骤2中可以避免这种情况

为了避免由 From: 域导致的错误,我们要用一个 if 来检查 sender 是不是 None如果是一个空字段的话,用 s_email 和 s_name 的值来取代 None ,这样脚本就可以继续运行而不是意外中断

虽然这个教程让使用正则表达式看起来很简单(Pandas在下面)但是也要求你有一定实际经验例如,我们知道使用if-else语句来检查数据是否存在事实上,之所以我们知道如何处理,是因为我们在写这个脚本时反复地尝试过编写代码是一个迭代过程值得注意的是,即使教程看起来是线性的,即使教程看起来是直截了当的,但实践中需要更多的尝试

第二步中使用了一个之前熟悉的正则表达式 \w\S*@.*\w, 用来 匹配实际的邮件地址格式

我们用不同的规则来命名,每一个名字的左边都用 From: 字段中的:来分割,电子邮件的右边用开括号 因此可以用 :.* 形式来找邮件名称 我们从每个结果中快速的去掉 : 和

现在,让我们打印出代码的结果来看看

注意我们没有使用 sender 变量在 arch()函数中作为搜索字符串我们已经打印了 sender 和 oup() 的类型,这样就能看到区别看起来 sender 是一个 re 的匹配对象,并且不能用arch()来搜索然而oup() 是一个字符串,而 arch 接受的参数即是字符串形式

我们来看看 s_email 和 s_name 长什么样子

同样,我们得到了匹配的对象每次对字符串进行arch() 操作, 都会生成匹配对象, 我们必须将其转换为字符串对象

在转换之前,回想一下如果没有From: 字段,,sender 的值将会是None,那么 s_email和s_name 的值也将为None因此,我们必须再次进行检查,以便脚本不会意外中断先看看如何针对s_email 构造代码

在步骤3A中,我们使用了if 语句来检查s_email的值是否为 None, 否则将抛出错误并中断脚本

然后,我们只需将s_email 匹配的对象转换为字符串并将其分配给变量sender_email 即可将转换完的字符串添加到 emails_dict 字典中,以便后续能极其方便地转换为pandas数据结构

在步骤3B中,我们对 s_name 进行几乎一致的操作.

就像之前做的一样,我们在步骤3B中首先检查s_name 的值是否为None

然后,在将字符串分配给变量前,我们调用两次了 re 模块中的b() 函数首先,通过用空字符“”代替:\s* ,删除冒号及冒号与姓名之间的任何空格字符然后删除姓名另一侧的空格字符和角括号,再次使用空字符进行替换终,将字符串分配给 sender_name并添加到字典中

让我们检查下结果

非常棒我们已经分离了邮箱地址和发件人姓名, 还将它们都添加到了字典中,接下来很快就能用上

既然我们已经得到了发件人的邮箱地址和姓名,通过同样的步骤就能获得收件人的邮箱地址和姓名并保存到字典中去

首先,我们找到To: 字段

接下来,我们将先发制人,避免recipient 为None的情况发生

如果 recipient 不为 None, 使用 arch() 来查找包含发件人邮箱地址和姓名的匹配对象,否则,我们将传递None值给 r_email 和 r_name

然后我们将匹配对象转换为字符串并添加至字典中去

因为From: 和 To: 字段具有相同的结构,因此我们可以对两者使用相同的代码,但对其他字段来说,我们需要定制稍微不同的代码

获取邮件的日期

现在让我们来获取邮件的发送日期

我们获取的Date:字段的代码与From:及To:字段的代码相同就像保证这两个字段的值不是None一样,我们同样要检查被赋值到变量date_field的值是否为 None

我们已经输出 date_oup(),因此可以更清楚地看到这一字符串的结构,它包含了邮件发送当天的具体日期并以“日-月-年” 的格式呈现,同时还包含了时间,但我们只想知道日期 得到日期的代码与得到姓名和邮件地址的代码非常相似,但更简单一些,可能这儿的疑惑点是正则表达式:\d+\s\w+\s\d+

日期是以数字开始的,因此我们可以用 \d 来解析它,就像日期格式中具体天数部分一样,它可能是由一位或者两位数字组成,所以在此+ 就变得非常重要了在正则表达式里, 在+ 的左侧来匹配一个或多个模式实例用\d+ 来匹配可以不用考虑日期的具体天数是一位还是两位数字

之后的一个空格可以通过寻找空白字符的 \s 来解析月份是由三个字母组成的,因此使用\w+ 来解析,再接另一个空格,所以继续用 \s 解析因为年份是由多个数字组成,所以我们需要再用一次\d+

表达式 \d+\s\w+\s\d+之所以能起作用,是因为精确的模式匹配约束着空格之间的内容

接下来,我们做和之前相同的 None 值检查

如果 date 不为 None ,我们就把它从这个匹配对象转换成一个字符串,然后赋值给变量 date_sent,再将其键值添加到字典中

进行下一步前,我们应特别注意的是+ 和 * 看起来很相似,但是它们差异很大用日期字符串来举例:

如果使用 * 我们将匹配到大于等于零个的结果,而 + 匹配大于等于一个的结果参照以上示例,我们输出了两种不同的结果,它们之间存在非常大的差异正如所见, + 可以解析出整个日期而*只解析出一个空格和数字1

接下来讲解邮件的标题

获得邮件的标题

我们可以像之前一样,用相同的代码架构来获取我们需要的信息

现在我们对正则表达式的格式已经很熟悉了对吧这个代码与之前的类似,为获得标题,我们可以用一个空的字符串来代替Subject:

获取邮件的内容

要添加到字典里的一项就是邮件的内容了

将标题从邮件内容中分离出来是非常复杂的任务,尤其当文中有很多不同形式的标题在原始混乱的数据中是很难找到一致性的规律,但是幸运的是这个工作有人帮我们解决了——Python的email 模块包非常适用这项任务

我们之前已经导入了email模块. 现在

,我们将 message_from_string()方法应用于item, 将整个email转换成 email消息对象. 一个消息对象由消息头和消息体组成, 分别对应于email的头部和主体.

接下来, 我们对email消息对象使用 get_payload()方法. 提取email内容. 并将内容传递给变量 body, 稍后我们会将其存储在字典 emails_dict 的键 email_body下.

在处理邮件正文时为什么选择email包而非正则表达式

你可能会疑惑, 为什么使用 email 包而不是正则表达式呢? 因为在不需要大量的清理工作时,正则表达式并不是的方法我们需要为这段代码做详细解释

我们值得探讨为何会作出这个选择但在开始之前,我们需要先理解方括号[ ] 在正则表达式中的含义, .

[ ] 用于匹配所有被它括起来的内容. 比如, 如果需要在字符串中查找 a, b, 或 c , 可以使用 [abc] 作为模式. 上文提到过的模式也适用[\w\s] 用于查找字母、数字或空格不同之处在于,它匹配的是方括号中的文字部分

现在,可以更好的理解我们为何会决定选择email模块了

仔细留意下数据就会发现email头部采用字符串 Status: 0 或 Status: R0作为结束,并在下一封邮件的 From r 字符串前结束,我们可以使用 Status:\s*\w*\n*[\s\S]*From\sr* 来获取email内容. [\s\S]* 用来查找空格或非空格字符,所以用于大段的文本、数字,以及标点符号

不幸的是一封 email 不止一个“Status: ” 字符串,也并不一定都包含 From r,即邮件拆分之后的数目可能会比邮件列表的字典数目多 也可能会比它少 ,但它们不会和已有的其他类别相匹配如果使用 pandas 包来解决这个问题的话 会遇到问题 ,因此,我们选择使用 email 包

创建字典列表

,添加字典emails_dict到 emails 列表:

此时可以打印emails列表执行 print(len(emails_dict)) 函数,查看列表中有多少字典和email 如前述,全部语料库包含 3977个email我们的小型测试文件中只有7个全部代码如下:

我们已经打印出了emails 列表的项, 它是由键和键值对组成的字典. 由于使用了 for 循环,因此每个字典拥有相同的键,但键值不同

我们为每个 item 赋值 email content here ,所以不需要打印所有的email来占据电脑屏幕. 如果你在家应用时打印email,你将会看到实际的email内容

使用pandas处理数据

如果使用 pandas 库处理列表中的字典 那将非常简单每个键会变成列名, 而键值变成行的内容

我们需要做的就是使用如下代码:

通过上面这行代码,使用pandas的DataFrame() 函数,我们将字典组成的 emails 转换成数据帧,并赋给变量emails_df.

就这么简单我们已经拥有了一个精致的Pandas数据帧,实际上它是一个简洁的表格,包含了从email中提取的所有信息

请看下数据帧的前几行:

The ad() 函数显示了数据序列的前几行该函数接受1个参数一个可选的参数用于定义需要显示的行数, n=3 表示前3行

也可以精确地查找例如,查找从特定域名发来的邮件但是,我们需要先学习一种新的正则表达式来完成精确查询工作

管道符号, |, 用于查找位于它两边的任意字符 如, a|b查找 a 或 b

有点类似 [ ], 但二者有区别假设我们需要查找crab, lobster, 或 isopod 使用 crab|lobster|isopod 会比 [crablobsterisopod] 更精确,前者会匹配完整单词,而后者只匹配单个字符

现在我们可以使用 | 符号查找从特定域名发送来的email

这里我们使用了一行超长的代码由内及外剖析它

emails_df[sender_email] 选择了标记为 sender_email的列,接下来,如果在该列中匹配到 子字符串 maktoob 或 spinfinder ,则ntains(maktoob|spinfinder) 返回 True . , 外面的emails_df[] 返回 sender_email 列视图,该列包含需要匹配的目标字符串干的漂亮

我们也可以单个检视邮件 只需要以下4步 第1步,查找包含字符串@maktoob的列 sender_email 对应的行索引请留意我们是如何使用正则表达式来完成这项任务的

第2步,使用索引查找email地址, loc[] 方法返回一系列不同属性的对象. 并将其打印出来,以便查看

第3步,从这一系列对象中提取email地址,并罗列出来,现在你会发现他的类型是now类

第4步将展示提取到的email正文

在第四步中 emails_df[sender_email] == james_ngola2002@ 是用来查找包含 james_ngola2002@ 的邮件发送者列,接下来 [email_body].values 用来查找邮件正文的相同行的列值,输出该列值

如你所见,我们可以多种方式应用正则表达式,正则表达式也能与pandas完美配合

其他资源

自从应用范围从生物学扩展到工程领域,过去这些年正则表达式发展速度惊人 今天,正则表达式已可在多种变成语言中应用,除基本模式外,有适当变化在这份教程中,我们使用Python练习使用正则表达式,但如果你喜欢,也可以使用 Stack Overflow 发掘它的其他特点用一张表格比较了不同正则表达式引擎的特点

正则表达式还有很多特性本教程不能一一列举,完整的文档可以参考Python文档中的 re 模块. 谷歌也有一份快速参考手册

如果需要一系列数据进行实验的话, Kaggle 和 StatsModels 将对你有所帮助(公众号:)

这里是正则表达式的速查表,但对大多数来说也是有帮助的

如果这篇教程对你有用的话,你也会喜欢Dataquest的正则表达式课程

原文链接:

雷锋字幕组正在招募中

扫描下方二维码

备注“雷锋字幕组+姓名”加入我们吧

原创文章,未经授权禁止转载详情见转载须知

南京肛泰中医医院电话
北京治疗白癜风医院
长沙治疗白癜风医院

相关文章

一周热门

热点排行

热门精选

友情链接:
媒体合作:

Copyright (c) 2011 八零CMS 版权所有 备案号:苏ICP备17012668号-1

网站地图