Jsoup学习总结

Jsoup学习总结

摘要

Jsoup是一款比较好的Java版HTML解析器。可直接解析某个URL地址、HTML文本内容。它提供了一套很是省力的API,可经过DOM,CSS以及相似于jQuery的操做方法来取出和操做数据。php

jSOUP主要功能

  1. 从一个URL,文件或字符串中解析HTML;
  2. 使用DOM或CSS选择器来查找、取出数据;
  3. 可操做HTML元素、属性、文本

环境搭建

MAVEN依赖css

  1. <dependency>
  2. <groupId>org.jsoup</groupId>
  3. <artifactId>jsoup</artifactId>
  4. <version>1.8.3</version>
  5. </dependency>

1. 输入

jsoup 能够从包括字符串、URL地址以及本地文件来加载HTML 文档,并生成Document对象实例。html

  • Document对象(一个文档的对象模型):文档由多个Elements和TextNodes组成 (以及其它辅助nodes:详细可查看:nodes package tree). 
    其继承结构以下:Document继承Element继承NodeTextNode继承 Node.java

  • 一个Element包含一个子节点集合,并拥有一个父Element。他们还提供了一个惟一的子元素过滤列表。node

1.1 从字符串中输入HTML文档

1.1.1 解析一个html字符串

使用静态方法Jsoup.parse(String html) 或 Jsoup.parse(String html, String baseUri)jquery

  1. String html = "<html><head><title>开源中国社区</title></head>"
  2. +"<body><p>这里是jsoup 项目的相关文章</p></body></html>";
  3. Document doc = Jsoup.parse(html);

说明:css3

  1. 其解析器可以尽最大可能从你提供的HTML文档来创见一个干净的解析结果,不管HTML的格式是否完整。好比它能够处理:web

    • 没有关闭的标签 (好比: <p>Lorem <p>Ipsum 解析为 <p>Lorem</p> <p>Ipsum</p>)
    • 隐式标签 (好比:它能够自动将 <td>Table data</td>包装成<table><tr><td>?)
    • 建立可靠的文档结构(html标签包含head 和 body,在head只出现恰当的元素)
  2. parse(String html, String baseUri) 这个方法可以将输入的HTML解析为一个新的文档 (Document),参数 baseUri 是用来将相对 URL 转成绝对URL,并指定从哪一个网站获取文档。如这个方法不适用,你可使用 parse(String html) 方法来解析成HTML字符串如上面的示例。正则表达式

  3. 只要解析的不是空字符串,就能返回一个结构合理的文档,其中包含(至少) 一个head和一个body元素。django

  4. 一旦拥有了一个Document,你就可使用Document中适当的方法或它父类 Element 和 Node 中的方法来取得相关数据

1.1.2 解析一个body片段

假如你有一个HTML片段 (好比. 一个 div 包含一对 p 标签; 一个不完整的HTML文档) 想对它进行解析。这个HTML片段能够是用户提交的一条评论或在一个CMS页面中编辑body部分。可使用Jsoup.parseBodyFragment(String html) 方法.

  1. String html = "<div><p>Lorem ipsum.</p>";
  2. Document doc = Jsoup.parseBodyFragment(html);
  3. Element body = doc.body();

说明:

  1. parseBodyFragment 方法建立一个空壳的文档,并插入解析过的HTML到body元素中。假如你使用正常的 Jsoup.parse(String html) 方法,一般你也能够获得相同的结果,可是明确将用户输入做为 body片断处理,以确保用户所提供的任何糟糕的HTML都将被解析成body元素。
  2. Document.body() 方法可以取得文档body元素的全部子元素,与 doc.getElementsByTag("body")相同。

1.2 从URL直接加载HTML文档

从一个网站获取和解析一个HTML文档,并查找其中的相关数据,可使用 Jsoup.connect(String url)方法。

  1. Document doc =Jsoup.connect("网址/").get();
  2. String title = doc.title();
  3. Document doc =Jsoup.connect("网址/")
  4. .data("query", "Java") //请求参数
  5. .userAgent("I’mjsoup") //设置User-Agent
  6. .cookie("auth", "token") //设置cookie
  7. .timeout(3000) //设置链接超时时间
  8. .post(); //使用POST方法访问URL

说明:

  1. connect(String url) 方法建立一个新的 Connection 和 get() 取得和解析一个HTML文件。若是从该URL获取HTML时发生错误,便会抛出 IOException,应适当处理。
  2. Connection 接口还提供一个方法链来解决特殊请求,具体以下:
  1. Document doc = Jsoup.connect("http://example.com")
  2. .data("query", "Java")
  3. .userAgent("Mozilla")
  4. .cookie("auth", "token")
  5. .timeout(3000)
  6. .post();

这个方法只支持Web URLs (http 和 https 协议); 假如你须要从一个文件加载,可使用 parse(File in, String charsetName) 代替。

1.3 从文件中加载HTML文档

在本机硬盘上有一个HTML文件,须要对它进行解析从中抽取数据或进行修改。可使用静态 Jsoup.parse(File in, String charsetName, String baseUri) 方法。

  1. File input = new File("/tmp/input.html");
  2. Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");

说明:

  1. parse(File in, String charsetName, String baseUri) 这个方法用来加载和解析一个HTML文件。如在加载文件的时候发生错误,将抛出IOException,应做适当处理。

  2. baseUri参数用于解决文件中URLs是相对路径的问题。若是不须要能够传入一个空的字符串。

  3. 另外还有一个方法parse(File in, String charsetName) 它使用文件的路径作为 baseUri。 这个方法适用于若是被解析文件位于网站的本地文件系统,且相关连接也指向该文件系统。

1.4 保证安全Stay safe

假如你可让用户输入HTML内容,那么要当心避免跨站脚本攻击。利用基于Whitelist的清除器和clean(String bodyHtml, Whitelist whitelist) 方法来清除用户输入的恶意内容。

详情查看 第4节 [4.HTML清理](#4.HTML清理)

2. 数据抽取

2.1 使用DOM方法来遍历一个文档

有一个HTML文档要从中提取数据,并了解这个HTML文档的结构。将HTML解析成一个Document以后,就可使用相似于DOM的方法进行操做。

  1. File input = new File("/tmp/input.html");
  2. Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
  3. Element content = doc.getElementById("content");
  4. Elements links = content.getElementsByTag("a");
  5. for (Element link : links) {
  6. String linkHref = link.attr("href");
  7. String linkText = link.text();
  8. }

说明:Elements这个对象提供了一系列相似于DOM的方法来查找元素,抽取并处理其中的数据。具体以下:

2.1.1 查找元素

getElementById(String id) id
getElementsByTag(String tag) 标签名
getElementsByClass(String className) class名
getElementsByAttribute(String key) 属性
siblingElements() 全部的兄弟元素
firstElementSibling() 第一个兄弟元素
lastElementSibling() 最后一个兄弟元素
nextElementSibling() 下一个兄弟元素
previousElementSibling() 上一个兄弟元素
parent() 获取该元素父节点
children() 获取该元素的子元素
child(int index) 获取该元素的第几个子元素(下标从0开始)

2.1.2 元素数据

attr(String key) 获取属性
attr(String key, String value) 设置属性
attributes() 获取全部属性
id() 获取该元素id
className() 获取该元素class,多个class之间空格隔开
classNames() 获取全部元素的class
text() 获取文本内容
text(String value) 设置文本内容
html() 获取元素内HTML
html(String value) 设置元素内的HTML内容
outerHtml() 获取元素外HTML内容
data() 获取数据内容(例如:script和style标签)
tag()  
tagName() 获取元素标签名

2.1.3 操做HTML和文本

append(String html) 添加给定的html到元素末尾
prepend(String html) 添加给定html到元素前面
appendText(String text) 建立并添加文本
prependText(String text) 建立并添加文本
appendElement(String tagName) 添加到元素末尾
prependElement(String tagName) 添加到元素前
html(String value) 设置元素值

2.2 使用选择器语法来查找元素(select)

使用 Element.select(String selector) 和 Elements.select(String selector),使用相似于CSS或jQuery的语法来查找和操做元素。

  1. File input = new File("/tmp/input.html");
  2. Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
  3. Elements links = doc.select("a[href]"); //带有href属性的a元素
  4. Elements pngs = doc.select("img[src$=.png]"); //扩展名为.png的图片
  5. Element masthead = doc.select("div.masthead").first();//class等于masthead的div标签
  6. Elements resultLinks = doc.select("h3.r > a"); //在h3元素以后的a元素

说明: 
jsoup elements对象支持相似于CSS (或jquery)的选择器语法,来实现很是强大和灵活的查找功能。

这个select 方法在DocumentElement,或Elements 对象中均可以使用。且是上下文相关的,所以可实现指定元素的过滤,或者链式选择访问。

Select方法将返回一个Elements 集合,并提供一组方法来抽取和处理结果。

2.2.1 Selector选择器 基本用法

tagname 使用标签名来定位,例如 a
ns|tag 使用命名空间的标签订位,例如 fb:name 来查找 <fb:name> 元素
#id 使用元素 id 定位,例如 #logo
.class 使用元素的 class 属性定位,例如 .head
[attribute] 使用元素的属性进行定位,例如 [href] 表示检索具备 href 属性的全部元素
[^attr] 使用元素的属性名前缀进行定位,例如 [^data-] 用来查找 HTML5 的 dataset 属性
[attr=value] 使用属性值进行定位,例如 [width=500] 定位全部 width 属性值为 500 的元素
[attr^=value], [attr$=value], [attr*=value] 利用匹配属性值开头、结尾或包含属性值来查找元素,好比:[href*=/path/]
[attr~=regex] 利用属性值匹配正则表达式来查找元素,例如img[src~=(?i).(png|jpe?g)]
* 定位全部元素

2.2.2 Selector选择器 组合使用

el#id 定位 id 值某个元素,例如 a#logo -> <a id=logo href= … >
el.class 定位 class 为指定值的元素,例如 div.head -> <div class="head">xxxx</div>
el[attr] 定位全部定义了某属性的元素,例如 a[href]
以上三个任意组合 例如 a[href]#logo 、a[name].outerlink
ancestor child 查找某个元素下子元素,好比:能够用.body p 查找在"body"元素下的全部 p元素
parent > child 查找某个父元素下的直接子元素,好比:能够用div.content > p 查找 p 元素,也能够用body > * 查找body标签下全部直接子元素
siblingA + siblingB 查找在A元素以前第一个同级元素B,好比:div.head + div
siblingA ~ siblingX 查找A元素以前的同级X元素,好比:h1 ~ p
el, el, el 多个选择器组合,查找匹配任一选择器的惟一元素,例如:div.masthead, div.logo

2.2.3 伪选择器selectors (表达式):

:lt(n) 查找哪些元素的同级索引值(它的位置在DOM树中是相对于它的父节点)小于n,好比:td:lt(3) 表示小于三列的元素
:gt(n) 查找哪些元素的同级索引值大于n``,好比: div p:gt(2)表示哪些div中有包含2个以上的p元素
:eq(n) 查找哪些元素的同级索引值与n相等,好比:form input:eq(1)表示包含一个input标签的Form元素
:has(seletor) 查找匹配选择器包含元素的元素,好比:div:has(p)表示哪些div包含了p元素
:not(selector) 查找与选择器不匹配的元素,好比: div:not(.logo) 表示不包含 class="logo" 元素的全部 div 列表
:contains(text) 查找包含给定文本的元素,不区分大不写,好比: p:contains(jsoup)
:containsOwn(text) 查找文本信息彻底等于指定条件的元素
:matches(regex) 使用正则表达式进行文本过滤:div:matches((?i)login)
:matchesOwn(regex) 使用正则表达式找到自身的文本
  • 注意:上述伪选择器索引是从0开始的,也就是说第一个元素索引值为0,第二个元素index为1等
  • 能够查看Selector API参考来了解更详细的内容

2.3 从元素抽取属性,文本和HTML

  1. String html = "<p>An <a href='http://example.com/'><b>example</b></a> link.</p>";
  2. Document doc = Jsoup.parse(html);//解析HTML字符串返回一个Document实现
  3. Element link = doc.select("a").first();//查找第一个a元素
  4. String text = doc.body().text(); // "An example link"//取得字符串中的文本
  5. String linkHref = link.attr("href"); // "http://example.com/"//取得连接地址
  6. String linkText = link.text(); // "example""//取得连接地址中的文本
  7. String linkOuterH = link.outerHtml();
  8. // "<a href="http://example.com"><b>example</b></a>"
  9. String linkInnerH = link.html(); // "<b>example</b>"//取得连接内的html内容

说明: 
上述方法是元素数据访问的核心办法。此外还其它一些方法可使用:

这些访问器方法都有相应的setter方法来更改数据.

2.4 处理URLs

有一个包含相对URLs路径的HTML文档,须要将这些相对路径转换成绝对路径的URLs。

  1. 在解析文档时确保有指定baseURI
  2. 而后使用 abs: 属性前缀来取得包含baseURI的绝对路径
  1. Document doc = Jsoup.connect("http://www.open-open.com").get();
  2. Element link = doc.select("a").first();
  3. String relHref = link.attr("href"); // == "/"
  4. String absHref = link.attr("abs:href"); // "http://www.open-open.com/"

说明: 
在HTML元素中,URLs常常写成相对于文档位置的相对路径: <a href="/download">...</a>. 当你使用 Node.attr(String key) 方法来取得a元素的href属性时,它将直接返回在HTML源码中指定的值。

假如你须要取得一个绝对路径,须要在属性名前加 abs: 前缀。这样就能够返回包含根路径的URL地址attr("abs:href"),所以,在解析HTML文档时,定义baseURI很是重要。

若是你不想使用abs: 前缀,还有一个方法可以实现一样的功能 Node.absUrl(String key)

2.5 示例程序: 获取全部连接

这个示例程序将展现如何从一个URL得到一个页面。而后提取页面中的全部连接、图片和其它辅助内容。并检查URLs和文本信息。运行下面程序须要指定一个URLs做为参数

  1. package org.jsoup.examples;
  2. import org.jsoup.Jsoup;
  3. import org.jsoup.helper.Validate;
  4. import org.jsoup.nodes.Document;
  5. import org.jsoup.nodes.Element;
  6. import org.jsoup.select.Elements;
  7. import java.io.IOException;
  8. /**
  9. * Example program to list links from a URL.
  10. */
  11. public class ListLinks {
  12. public static void main(String[] args) throws IOException {
  13. Validate.isTrue(args.length == 1, "usage: supply url to fetch");
  14. String url = args[0];
  15. print("Fetching %s...", url);
  16. Document doc = Jsoup.connect(url).get();
  17. Elements links = doc.select("a[href]");
  18. Elements media = doc.select("[src]");
  19. Elements imports = doc.select("link[href]");
  20. print("\nMedia: (%d)", media.size());
  21. for (Element src : media) {
  22. if (src.tagName().equals("img"))
  23. print(" * %s: <%s> %sx%s (%s)",
  24. src.tagName(), src.attr("abs:src"), src.attr("width"), src.attr("height"),
  25. trim(src.attr("alt"), 20));
  26. else
  27. print(" * %s: <%s>", src.tagName(), src.attr("abs:src"));
  28. }
  29. print("\nImports: (%d)", imports.size());
  30. for (Element link : imports) {
  31. print(" * %s <%s> (%s)", link.tagName(),link.attr("abs:href"), link.attr("rel"));
  32. }
  33. print("\nLinks: (%d)", links.size());
  34. for (Element link : links) {
  35. print(" * a: <%s> (%s)", link.attr("abs:href"), trim(link.text(), 35));
  36. }
  37. }
  38. private static void print(String msg, Object... args) {
  39. System.out.println(String.format(msg, args));
  40. }
  41. private static String trim(String s, int width) {
  42. if (s.length() > width)
  43. return s.substring(0, width-1) + ".";
  44. else
  45. return s;
  46. }
  47. }

示例输入结果:

  1. Fetching http://news.ycombinator.com/...
  2. Media: (38)
  3. * img: <http://ycombinator.com/images/y18.gif> 18x18 ()
  4. * img: <http://ycombinator.com/images/s.gif> 10x1 ()
  5. * img: <http://ycombinator.com/images/grayarrow.gif> x ()
  6. * img: <http://ycombinator.com/images/s.gif> 0x10 ()
  7. * script: <http://www.co2stats.com/propres.php?s=1138>
  8. * img: <http://ycombinator.com/images/s.gif> 15x1 ()
  9. * img: <http://ycombinator.com/images/hnsearch.png> x ()
  10. * img: <http://ycombinator.com/images/s.gif> 25x1 ()
  11. * img: <http://mixpanel.com/site_media/images/mixpanel_partner_logo_borderless.gif> x (Analytics by Mixpan.)
  12. Imports: (2)
  13. * link <http://ycombinator.com/news.css> (stylesheet)
  14. * link <http://ycombinator.com/favicon.ico> (shortcut icon)
  15. Links: (141)
  16. * a: <http://ycombinator.com> ()
  17. * a: <http://news.ycombinator.com/news> (Hacker News)
  18. * a: <http://news.ycombinator.com/newest> (new)
  19. * a: <http://news.ycombinator.com/newcomments> (comments)
  20. * a: <http://news.ycombinator.com/leaders> (leaders)
  21. * a: <http://news.ycombinator.com/jobs> (jobs)
  22. * a: <http://news.ycombinator.com/submit> (submit)
  23. * a: <http://news.ycombinator.com/x?fnid=JKhQjfU7gW> (login)
  24. * a: <http://news.ycombinator.com/vote?for=1094578&dir=up&whence=%6e%65%77%73> ()
  25. * a: <http://www.readwriteweb.com/archives/facebook_gets_faster_debuts_homegrown_php_compiler.php?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+readwriteweb+%28ReadWriteWeb%29&utm_content=Twitter> (Facebook speeds up PHP)
  26. * a: <http://news.ycombinator.com/user?id=mcxx> (mcxx)
  27. * a: <http://news.ycombinator.com/item?id=1094578> (9 comments)
  28. * a: <http://news.ycombinator.com/vote?for=1094649&dir=up&whence=%6e%65%77%73> ()
  29. * a: <http://groups.google.com/group/django-developers/msg/a65fbbc8effcd914> ("Tough. Django produces XHTML.")
  30. * a: <http://news.ycombinator.com/user?id=andybak> (andybak)
  31. * a: <http://news.ycombinator.com/item?id=1094649> (3 comments)
  32. * a: <http://news.ycombinator.com/vote?for=1093927&dir=up&whence=%6e%65%77%73> ()
  33. * a: <http://news.ycombinator.com/x?fnid=p2sdPLE7Ce> (More)
  34. * a: <http://news.ycombinator.com/lists> (Lists)
  35. * a: <http://news.ycombinator.com/rss> (RSS)
  36. * a: <http://ycombinator.com/bookmarklet.html> (Bookmarklet)
  37. * a: <http://ycombinator.com/newsguidelines.html> (Guidelines)
  38. * a: <http://ycombinator.com/newsfaq.html> (FAQ)
  39. * a: <http://ycombinator.com/newsnews.html> (News News)
  40. * a: <http://news.ycombinator.com/item?id=363> (Feature Requests)
  41. * a: <http://ycombinator.com> (Y Combinator)
  42. * a: <http://ycombinator.com/w2010.html> (Apply)
  43. * a: <http://ycombinator.com/lib.html> (Library)
  44. * a: <http://www.webmynd.com/html/hackernews.html> ()
  45. * a: <http://mixpanel.com/?from=yc> ()

3. 数据修改

3.1 设置属性的值

在解析一个Document以后可能想修改其中的某些属性值,而后再保存到磁盘或都输出到前台页面。

可使用属性设置方法 Element.attr(String key, String value), 和 Elements.attr(String key, String value).

假如你须要修改一个元素的 class 属性,可使用 Element.addClass(String className) 和Element.removeClass(String className) 方法。

Elements 提供了批量操做元素属性和class的方法,好比:要为div中的每个a元素都添加一个rel="nofollow" 可使用以下方法:

  1. doc.select("div.comments a").attr("rel", "nofollow");

说明:与Element 中的其它方法同样,attr 方法也是返回当前 Element (或在使用选择器是返回 Elements 集合)。这样可以很方便使用方法连用的书写方式。好比:

  1. doc.select("div.masthead").attr("title", "jsoup").addClass("round-box");

3.2 设置一个元素的HTML内容

  1. Element div = doc.select("div").first(); // <div></div>
  2. div.html("<p>lorem ipsum</p>"); // <div><p>lorem ipsum</p></div>
  3. div.prepend("<p>First</p>");//在div前添加html内容
  4. div.append("<p>Last</p>");//在div以后添加html内容
  5. // 添完后的结果: <div><p>First</p><p>lorem ipsum</p><p>Last</p></div>
  6. Element span = doc.select("span").first(); // <span>One</span>
  7. span.wrap("<li><a href='http://example.com/'></a></li>");
  8. // 添完后的结果: <li><a href="http://example.com"><span>One</span></a></li>

说明:

3.3 设置元素的文本内容

  1. Element div = doc.select("div").first(); // <div></div>
  2. div.text("five > four"); // <div>five &gt; four</div>
  3. div.prepend("First ");
  4. div.append(" Last");
  5. // now: <div>First five &gt; four Last</div>

说明: 
文本设置方法与 HTML setter 方法同样:

对于传入的文本若是含有像 <> 等这样的字符,将以文本处理,而非HTML。

4.HTML清理

4.1 消除不受信任的HTML (防止XSS攻击)

在作网站的时候,常常会提供用户评论的功能。有些不怀好意的用户,会搞一些脚本到评论内容中,而这些脚本可能会破坏整个页面的行为,更严重的是获取一些机要信息,此时须要清理该HTML,以免跨站脚本cross-site scripting攻击(XSS)。 
使用jsoup HTML Cleaner 方法进行清除,但须要指定一个可配置的 Whitelist

  1. String unsafe = "<p><a href='http://example.com/' onclick='stealCookies()'>Link</a></p>";
  2. String safe = Jsoup.clean(unsafe, Whitelist.basic());
  3. // now: <p><a href="http://example.com/" rel="nofollow">Link</a></p>

说明: 
XSS又叫CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。XSS属于被动式的攻击,由于其被动且很差利用,因此许多人常忽略其危害性。因此咱们常常只让用户输入纯文本的内容,但这样用户体验就比较差了。

一个更好的解决方法就是使用一个富文本编辑器WYSIWYG如CKEditor 和 TinyMCE。这些能够输出HTML并可以让用户可视化编辑。虽然他们能够在客户端进行校验,可是这样还不够安全,须要在服务器端进行校验并清除有害的HTML代码,这样才能确保输入到你网站的HTML是安全的。不然,攻击者可以绕过客户端的Javascript验证,并注入不安全的HMTL直接进入您的网站。

jsoup的whitelist清理器可以在服务器端对用户输入的HTML进行过滤,只输出一些安全的标签和属性。

jsoup提供了一系列的Whitelist基本配置,可以知足大多数要求;但若有必要,也能够进行修改,不过要当心。

这个cleaner很是好用不只能够避免XSS攻击,还能够限制用户能够输入的标签范围。

jsoup 使用一个 Whitelist 类用来对 HTML 文档进行过滤,该类提供几个经常使用方法:

4.2 whitelist经常使用方法

API查看:Whitelist

方法名 简介
none() 只容许包含文本信息
basic() 容许的标签包括:a, b, blockquote, br, cite, code, dd, dl, dt, em, i, li, ol, p, pre, q, small, strike, strong, sub, sup, u, ul, 以及合适的属性
simpleText() 只容许 b, em, i, strong, u 这些标签
basicWithImages() 在 basic() 的基础上增长了图片
relaxed() 这个过滤器容许的标签最多,包括:a, b, blockquote, br, caption, cite, code, col, colgroup, dd, dl, dt, em, h1, h2, h3, h4, h5, h6, i, img, li, ol, p, pre, q, small, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, u, ul

若是这五个过滤器都没法知足你的要求呢,例如你容许用户插入 flash 动画,不要紧,Whitelist 提供扩展功能,例如 whitelist.addTags("embed","object","param","span","div"); 也可调用 addAttributes 为某些元素增长属性。

参考:

  • 参阅XSS cheat sheet ,有一个例子能够了解为何不能使用正则表达式,而采用安全的whitelist parser-based清理器才是正确的选择。
  • 参阅Cleaner ,了解如何返回一个 Document 对象,而不是字符串
  • 参阅Whitelist,了解如何建立一个自定义的whitelist
  • nofollow 连接属性了解

5.更多参考

相关文章
相关标签/搜索