[
  {
    "path": ".gitignore",
    "content": "target/\npom.xml.tag\npom.xml.releaseBackup\npom.xml.versionsBackup\npom.xml.next\nrelease.properties\ndependency-reduced-pom.xml\nbuildNumber.properties\n.mvn/timing.properties\n# https://github.com/takari/maven-wrapper#usage-without-binary-jar\n.mvn/wrapper/maven-wrapper.jar\n\n# Eclipse m2e generated files\n# Eclipse Core\n.project\n# JDT-specific (Eclipse Java Development Tools)\n.classpath\n.metadata\nbin/\ntmp/\n*.tmp\n*.bak\n*.swp\n*~.nib\nlocal.properties\n.settings/\n.loadpath\n.recommenders\n\n# External tool builders\n.externalToolBuilders/\n\n# Locally stored \"Eclipse launch configurations\"\n*.launch\n\n# PyDev specific (Python IDE for Eclipse)\n*.pydevproject\n\n# CDT-specific (C/C++ Development Tooling)\n.cproject\n\n# CDT- autotools\n.autotools\n\n# Java annotation processor (APT)\n.factorypath\n\n# PDT-specific (PHP Development Tools)\n.buildpath\n\n# sbteclipse plugin\n.target\n\n# Tern plugin\n.tern-project\n\n# TeXlipse plugin\n.texlipse\n\n# STS (Spring Tool Suite)\n.springBeans\n\n# Code Recommenders\n.recommenders/\n\n# Annotation Processing\n.apt_generated/\n.apt_generated_test/\n\n# Scala IDE specific (Scala & Java development for Eclipse)\n.cache-main\n.scala_dependencies\n.worksheet\n\n# Uncomment this line if you wish to ignore the project description file.\n# Typically, this file would be tracked if it contains build/dependency configurations:\n#.project\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java \njdk:\n  - openjdk9\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"{}\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n   Copyright 2025 code4craft\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README-zh.md",
    "content": "![logo](http://webmagic.io/images/logo.jpeg)\n\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/us.codecraft/webmagic-parent/badge.svg?subject=Maven%20Central)](https://maven-badges.herokuapp.com/maven-central/us.codecraft/webmagic-parent/)\n[![License](https://img.shields.io/badge/License-Apache%20License%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)\n[![Build Status](https://travis-ci.org/code4craft/webmagic.png?branch=master)](https://travis-ci.org/code4craft/webmagic)\n\n官方网站[http://webmagic.io/](http://webmagic.io/)\n\n>webmagic是一个开源的Java垂直爬虫框架，目标是简化爬虫的开发流程，让开发者专注于逻辑功能的开发。webmagic的核心非常简单，但是覆盖爬虫的整个流程，也是很好的学习爬虫开发的材料。\n\n\nwebmagic的主要特色：\n\n* 完全模块化的设计，强大的可扩展性。\n* 核心简单但是涵盖爬虫的全部流程，灵活而强大，也是学习爬虫入门的好材料。\n* 提供丰富的抽取页面API。\n* 无配置，但是可通过POJO+注解形式实现一个爬虫。\n* 支持多线程。\n* 支持分布式。\n* 支持爬取js动态渲染的页面。\n* 无框架依赖，可以灵活的嵌入到项目中去。\n\nwebmagic的架构和设计参考了以下两个项目，感谢以下两个项目的作者：\n\npython爬虫 **scrapy** [https://github.com/scrapy/scrapy](https://github.com/scrapy/scrapy)\n\nJava爬虫 **Spiderman** [http://git.oschina.net/l-weiwei/spiderman](http://git.oschina.net/l-weiwei/spiderman)\n\nwebmagic的github地址：[https://github.com/code4craft/webmagic](https://github.com/code4craft/webmagic)。\n\n## 快速开始\n\n### 使用maven\n\nwebmagic使用maven管理依赖，在项目中添加对应的依赖即可使用webmagic：\n\n```xml\n<dependency>\n    <groupId>us.codecraft</groupId>\n    <artifactId>webmagic-core</artifactId>\n    <version>${webmagic.version}</version>\n</dependency>\n<dependency>\n    <groupId>us.codecraft</groupId>\n    <artifactId>webmagic-extension</artifactId>\n    <version>${webmagic.version}</version>\n</dependency>\n```\n        \nWebMagic 使用slf4j-log4j12作为slf4j的实现.如果你自己定制了slf4j的实现，请在项目中去掉此依赖。\n\n```xml\n<exclusions>\n    <exclusion>\n        <groupId>org.slf4j</groupId>\n        <artifactId>slf4j-log4j12</artifactId>\n    </exclusion>\n</exclusions>\n```\n\n#### 项目结构\n\t\nwebmagic主要包括两个包：\n\n* **webmagic-core**\n\t\n\twebmagic核心部分，只包含爬虫基本模块和基本抽取器。webmagic-core的目标是成为网页爬虫的一个教科书般的实现。\n\t\n* **webmagic-extension**\n\t\n\twebmagic的扩展模块，提供一些更方便的编写爬虫的工具。包括注解格式定义爬虫、JSON、分布式等支持。\n\t\nwebmagic还包含两个可用的扩展包，因为这两个包都依赖了比较重量级的工具，所以从主要包中抽离出来，这些包需要下载源码后自己编译：：\n\n* **webmagic-saxon**\n\n\twebmagic与Saxon结合的模块。Saxon是一个XPath、XSLT的解析工具，webmagic依赖Saxon来进行XPath2.0语法解析支持。\n\n* **webmagic-selenium**\n\n\twebmagic与Selenium结合的模块。Selenium是一个模拟浏览器进行页面渲染的工具，webmagic依赖Selenium进行动态页面的抓取。\n\t\n在项目中，你可以根据需要依赖不同的包。\n\n### 不使用maven\n\n在项目的**lib**目录下，有依赖的所有jar包，直接在IDE里import即可。\n\n### 第一个爬虫\n\n#### 定制PageProcessor\n\nPageProcessor是webmagic-core的一部分，定制一个PageProcessor即可实现自己的爬虫逻辑。以下是抓取osc博客的一段代码：\n\n```java\npublic class OschinaBlogPageProcessor implements PageProcessor {\n\n    private Site site = Site.me().setDomain(\"my.oschina.net\");\n\n    @Override\n    public void process(Page page) {\n        List<String> links = page.getHtml().links().regex(\"http://my\\\\.oschina\\\\.net/flashsword/blog/\\\\d+\").all();\n        page.addTargetRequests(links);\n        page.putField(\"title\", page.getHtml().xpath(\"//div[@class='BlogEntity']/div[@class='BlogTitle']/h1\").toString());\n        page.putField(\"content\", page.getHtml().$(\"div.content\").toString());\n        page.putField(\"tags\",page.getHtml().xpath(\"//div[@class='BlogTags']/a/text()\").all());\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new OschinaBlogPageProcessor()).addUrl(\"http://my.oschina.net/flashsword/blog\")\n             .addPipeline(new ConsolePipeline()).run();\n    }\n}\n```\n\n\n这里通过page.addTargetRequests()方法来增加要抓取的URL，并通过page.putField()来保存抽取结果。page.getHtml().xpath()则是按照某个规则对结果进行抽取，这里抽取支持链式调用。调用结束后，toString()表示转化为单个String，all()则转化为一个String列表。\n\nSpider是爬虫的入口类。Pipeline是结果输出和持久化的接口，这里ConsolePipeline表示结果输出到控制台。\n\n执行这个main方法，即可在控制台看到抓取结果。webmagic默认有3秒抓取间隔，请耐心等待。\n\n#### 使用注解\n\nwebmagic-extension包括了注解方式编写爬虫的方法，只需基于一个POJO增加注解即可完成一个爬虫。以下仍然是抓取oschina博客的一段代码，功能与OschinaBlogPageProcesser完全相同：\n\n```java\n@TargetUrl(\"http://my.oschina.net/flashsword/blog/\\\\d+\")\npublic class OschinaBlog {\n\n    @ExtractBy(\"//title\")\n    private String title;\n\n    @ExtractBy(value = \"div.BlogContent\",type = ExtractBy.Type.Css)\n    private String content;\n\n    @ExtractBy(value = \"//div[@class='BlogTags']/a/text()\", multi = true)\n    private List<String> tags;\n\n    public static void main(String[] args) {\n        OOSpider.create(\n        \tSite.me(),\n\t\t\tnew ConsolePageModelPipeline(), OschinaBlog.class).addUrl(\"http://my.oschina.net/flashsword/blog\").run();\n    }\n}\n```\n\n这个例子定义了一个Model类，Model类的字段'title'、'content'、'tags'均为要抽取的属性。这个类在Pipeline里是可以复用的。\n\n### 详细文档\n\n见[http://webmagic.io/docs/](http://webmagic.io/docs/)。\n\n### 示例\n\nwebmagic-samples目录里有一些定制PageProcessor以抽取不同站点的例子。\n\nwebmagic的使用可以参考：[oschina openapi 应用：博客搬家](https://git.oschina.net/yashin/MoveBlog)\n\n\n### 协议\n\nwebmagic遵循[Apache 2.0协议](http://opensource.org/licenses/Apache-2.0)\n\n### 邮件组:\n\nGmail：\n[https://groups.google.com/forum/#!forum/webmagic-java](https://groups.google.com/forum/#!forum/webmagic-java)\n\nQQ:\n[http://list.qq.com/cgi-bin/qf_invite?id=023a01f505246785f77c5a5a9aff4e57ab20fcdde871e988](http://list.qq.com/cgi-bin/qf_invite?id=023a01f505246785f77c5a5a9aff4e57ab20fcdde871e988)\n\n### QQ群：\n\n373225642(已满) 542327088\n\n### 相关项目：\n\n[Gather Platform](https://github.com/gsh199449/spider)\n\nGather Platform 数据抓取平台是一套基于Webmagic内核的,具有Web任务配置和任务管理界面的数据采集与搜索平台。\n"
  },
  {
    "path": "README.md",
    "content": "![logo](http://webmagic.io/images/logo.jpeg)\n\n[Readme in Chinese](https://github.com/code4craft/webmagic/tree/master/README-zh.md)\n\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/us.codecraft/webmagic-parent/badge.svg?subject=Maven%20Central)](https://maven-badges.herokuapp.com/maven-central/us.codecraft/webmagic-parent/)\n[![License](https://img.shields.io/badge/License-Apache%20License%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)\n[![Build Status](https://travis-ci.org/code4craft/webmagic.png?branch=master)](https://travis-ci.org/code4craft/webmagic)\n\n>A scalable crawler framework. It covers the whole lifecycle of crawler: downloading, url management, content extraction and persistent. It can simplify the development of a  specific crawler.\n\n## Features:\n\n* Simple core with high flexibility.\n* Simple API for html extracting.\n* Annotation with POJO to customize a crawler, no configuration.\n* Multi-thread and Distribution support.\n* Easy to be integrated.\n\n## Install:\n  \nAdd dependencies to your pom.xml:\n\n```xml\n<dependency>\n    <groupId>us.codecraft</groupId>\n    <artifactId>webmagic-core</artifactId>\n    <version>${webmagic.version}</version>\n</dependency>\n<dependency>\n    <groupId>us.codecraft</groupId>\n    <artifactId>webmagic-extension</artifactId>\n    <version>${webmagic.version}</version>\n</dependency>\n```\n        \nWebMagic use slf4j with slf4j-log4j12 implementation. If you customized your slf4j implementation, please exclude slf4j-log4j12.\n\n```xml\n<exclusions>\n    <exclusion>\n        <groupId>org.slf4j</groupId>\n        <artifactId>slf4j-log4j12</artifactId>\n    </exclusion>\n</exclusions>\n```\n\n\n## Get Started:\n\n### First crawler:\n\nWrite a class implements PageProcessor. For example, I wrote a crawler of github repository information.\n\n```java\npublic class GithubRepoPageProcessor implements PageProcessor {\n\n    private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);\n\n    @Override\n    public void process(Page page) {\n        page.addTargetRequests(page.getHtml().links().regex(\"(https://github\\\\.com/\\\\w+/\\\\w+)\").all());\n        page.putField(\"author\", page.getUrl().regex(\"https://github\\\\.com/(\\\\w+)/.*\").toString());\n        page.putField(\"name\", page.getHtml().xpath(\"//h1[@class='public']/strong/a/text()\").toString());\n        if (page.getResultItems().get(\"name\")==null){\n            //skip this page\n            page.setSkip(true);\n        }\n        page.putField(\"readme\", page.getHtml().xpath(\"//div[@id='readme']/tidyText()\"));\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new GithubRepoPageProcessor()).addUrl(\"https://github.com/code4craft\").thread(5).run();\n    }\n}\n```\n\n* `page.addTargetRequests(links)`\n\t\n\tAdd urls for crawling.\n    \nYou can also use annotation way:\n\n```java\n@TargetUrl(\"https://github.com/\\\\w+/\\\\w+\")\n@HelpUrl(\"https://github.com/\\\\w+\")\npublic class GithubRepo {\n\n    @ExtractBy(value = \"//h1[@class='public']/strong/a/text()\", notNull = true)\n    private String name;\n\n    @ExtractByUrl(\"https://github\\\\.com/(\\\\w+)/.*\")\n    private String author;\n\n    @ExtractBy(\"//div[@id='readme']/tidyText()\")\n    private String readme;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me().setSleepTime(1000)\n                , new ConsolePageModelPipeline(), GithubRepo.class)\n                .addUrl(\"https://github.com/code4craft\").thread(5).run();\n    }\n}\n```\n\t\t\n### Docs and samples:\n\nDocuments: [http://webmagic.io/docs/](http://webmagic.io/docs/)\n\nThe architecture of webmagic (referred to [Scrapy](http://scrapy.org/))\n\n![image](http://code4craft.github.io/images/posts/webmagic.png)\n\nThere are more examples in `webmagic-samples` package.\n\n### License:\n\nLicensed under [Apache 2.0 license](http://opensource.org/licenses/Apache-2.0)\n\n### Thanks:\n\nTo write webmagic, I refered to the projects below :\n\n* **Scrapy**\n\n\tA crawler framework in Python.\n \n\t[http://scrapy.org/](http://scrapy.org/)\n\n* **Spiderman**\n\n\tAnother crawler framework in Java.\n\t\n\t[http://git.oschina.net/l-weiwei/spiderman](http://git.oschina.net/l-weiwei/spiderman)\n\n### Mail-list:\n\n[https://groups.google.com/forum/#!forum/webmagic-java](https://groups.google.com/forum/#!forum/webmagic-java)\n\n[http://list.qq.com/cgi-bin/qf_invite?id=023a01f505246785f77c5a5a9aff4e57ab20fcdde871e988](http://list.qq.com/cgi-bin/qf_invite?id=023a01f505246785f77c5a5a9aff4e57ab20fcdde871e988)\n\nQQ Group: 373225642 542327088\n\n### Related Project\n\n* <a href=\"https://github.com/gsh199449/spider\" target=\"_blank\">Gather Platform</a>\n\t\n\tA web console based on WebMagic for Spider configuration and management.\n\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n    xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://maven.apache.org/POM/4.0.0\n        http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>org.oxerr</groupId>\n        <artifactId>oxerr-parent</artifactId>\n        <version>2.3.1</version>\n        <relativePath /> <!-- lookup parent from repository -->\n    </parent>\n    <groupId>us.codecraft</groupId>\n    <version>1.0.4-SNAPSHOT</version>\n    <packaging>pom</packaging>\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <maven.compiler.source>11</maven.compiler.source>\n        <maven.compiler.target>11</maven.compiler.target>\n        <assertj.version>3.23.1</assertj.version>\n        <commons-cli.version>1.5.0</commons-cli.version>\n        <commons-collections4.version>4.4</commons-collections4.version>\n        <commons-io.version>2.14.0</commons-io.version>\n        <commons-lang3.version>3.12.0</commons-lang3.version>\n        <fastjson.version>2.0.19.graal</fastjson.version>\n        <groovy-all.version>3.0.13</groovy-all.version>\n        <guava.version>32.0.0-jre</guava.version>\n        <htmlcleaner.version>2.29</htmlcleaner.version>\n        <httpclient.version>4.5.13</httpclient.version>\n        <httpcore.version>4.4.15</httpcore.version>\n        <jedis.version>3.7.1</jedis.version>\n        <jruby.version>9.4.12.1</jruby.version>\n        <json-path.version>2.9.0</json-path.version>\n        <junit.version>5.10.2</junit.version>\n        <junit.platform.version>1.10.2</junit.platform.version>\n        <jython.version>2.7.3</jython.version>\n        <log4j2.version>2.23.1</log4j2.version>\n        <mockito-all.version>2.0.2-beta</mockito-all.version>\n        <moco.version>1.3.0</moco.version>\n        <phantomjsdriver.version>1.2.0</phantomjsdriver.version>\n        <saxon-he.version>12.4</saxon-he.version>\n        <selenium-java.version>4.14.1</selenium-java.version>\n        <slf4j.version>2.0.4</slf4j.version>\n        <spring-version>4.0.0.RELEASE</spring-version>\n        <xsoup.version>0.3.5</xsoup.version>\n    </properties>\n    <artifactId>webmagic</artifactId>\n    <name>webmagic</name>\n    <description>\n        A crawler framework. It covers the whole lifecycle of crawler: downloading, url management, content\n        extraction and persistent. It can simply the development of a specific crawler.\n    </description>\n    <url>https://github.com/code4craft/webmagic/</url>\n    <developers>\n        <developer>\n            <id>code4craft</id>\n            <name>Yihua huang</name>\n            <email>code4crafer@gmail.com</email>\n        </developer>\n        <developer>\n            <id>yuany</id>\n            <name>Ligang Yao</name>\n            <email>ligang.yao@answers.com</email>\n        </developer>\n    </developers>\n    <scm>\n        <connection>scm:git:git@github.com:code4craft/webmagic.git</connection>\n        <developerConnection>scm:git:git@github.com:code4craft/webmagic.git</developerConnection>\n        <url>git@github.com:code4craft/webmagic.git</url>\n        <tag>WebMagic-${project.version}</tag>\n    </scm>\n    <licenses>\n        <license>\n            <name>Apache License, Version 2.0</name>\n            <url>http://www.apache.org/licenses/LICENSE-2.0</url>\n        </license>\n    </licenses>\n\n    <modules>\n        <module>webmagic-core</module>\n        <module>webmagic-extension/</module>\n        <module>webmagic-scripts/</module>\n        <module>webmagic-selenium</module>\n        <module>webmagic-saxon</module>\n        <module>webmagic-samples</module>\n        <module>webmagic-coverage</module>\n    </modules>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-slf4j2-impl</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-engine</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.vintage</groupId>\n            <artifactId>junit-vintage-engine</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.platform</groupId>\n            <artifactId>junit-platform-launcher</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.platform</groupId>\n            <artifactId>junit-platform-runner</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>org.mockito</groupId>\n                <artifactId>mockito-all</artifactId>\n                <version>${mockito-all.version}</version>\n                <scope>test</scope>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.httpcomponents</groupId>\n                <artifactId>httpclient</artifactId>\n                <version>${httpclient.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.httpcomponents</groupId>\n                <artifactId>httpcore</artifactId>\n                <version>${httpcore.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.logging.log4j</groupId>\n                <artifactId>log4j-core</artifactId>\n                <version>${log4j2.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.logging.log4j</groupId>\n                <artifactId>log4j-slf4j2-impl</artifactId>\n                <version>${log4j2.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.google.guava</groupId>\n                <artifactId>guava</artifactId>\n                <version>${guava.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.jayway.jsonpath</groupId>\n                <artifactId>json-path</artifactId>\n                <version>${json-path.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.junit.jupiter</groupId>\n                <artifactId>junit-jupiter-engine</artifactId>\n                <version>${junit.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.junit.vintage</groupId>\n                <artifactId>junit-vintage-engine</artifactId>\n                <version>${junit.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.junit.platform</groupId>\n                <artifactId>junit-platform-launcher</artifactId>\n                <version>${junit.platform.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.junit.platform</groupId>\n                <artifactId>junit-platform-runner</artifactId>\n                <version>${junit.platform.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.slf4j</groupId>\n                <artifactId>slf4j-api</artifactId>\n                <version>${slf4j.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>us.codecraft</groupId>\n                <artifactId>xsoup</artifactId>\n                <version>0.3.7</version>\n            </dependency>\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>fastjson</artifactId>\n                <version>${fastjson.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.github.dreamhead</groupId>\n                <artifactId>moco-core</artifactId>\n                <version>${moco.version}</version>\n                <scope>test</scope>\n                <exclusions>\n                    <exclusion>\n                        <groupId>org.slf4j</groupId>\n                        <artifactId>slf4j-simple</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n            <dependency>\n                <groupId>org.assertj</groupId>\n                <artifactId>assertj-core</artifactId>\n                <version>${assertj.version}</version>\n                <scope>test</scope>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.commons</groupId>\n                <artifactId>commons-lang3</artifactId>\n                <version>${commons-lang3.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.commons</groupId>\n                <artifactId>commons-collections4</artifactId>\n                <version>${commons-collections4.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>commons-io</groupId>\n                <artifactId>commons-io</artifactId>\n                <version>${commons-io.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.codehaus.groovy</groupId>\n                <artifactId>groovy-all</artifactId>\n                <version>${groovy-all.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.jruby</groupId>\n                <artifactId>jruby</artifactId>\n                <version>${jruby.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.python</groupId>\n                <artifactId>jython</artifactId>\n                <version>${jython.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.seleniumhq.selenium</groupId>\n                <artifactId>selenium-java</artifactId>\n                <version>${selenium-java.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>net.sf.saxon</groupId>\n                <artifactId>Saxon-HE</artifactId>\n                <version>${saxon-he.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>net.sourceforge.htmlcleaner</groupId>\n                <artifactId>htmlcleaner</artifactId>\n                <version>${htmlcleaner.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.github.detro</groupId>\n                <artifactId>phantomjsdriver</artifactId>\n                <version>${phantomjsdriver.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>commons-cli</groupId>\n                <artifactId>commons-cli</artifactId>\n                <version>${commons-cli.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>redis.clients</groupId>\n                <artifactId>jedis</artifactId>\n                <version>${jedis.version}</version>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <configuration>\n                    <doctitle>WebMagic ${project.version}</doctitle>\n                    <locale>en_US</locale>\n\n                    <!-- avoid the issue: https://bugs.openjdk.java.net/browse/JDK-8212233 -->\n                    <detectJavaApiLink>false</detectJavaApiLink>\n\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>aggregate</id>\n                        <goals>\n                            <goal>aggregate</goal>\n                        </goals>\n                        <phase>site</phase>\n                    </execution>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.jacoco</groupId>\n                <artifactId>jacoco-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>prepare-agent</goal>\n                        </goals>\n                    </execution>\n                    <execution>\n                        <id>report</id>\n                        <phase>verify</phase>\n                        <goals>\n                            <goal>report</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>com.amashchenko.maven.plugin</groupId>\n                <artifactId>gitflow-maven-plugin</artifactId>\n                <configuration>\n                    <gitFlowConfig>\n                        <versionTagPrefix>WebMagic-</versionTagPrefix>\n                    </gitFlowConfig>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "src/site/site.xml",
    "content": "<project xmlns=\"http://maven.apache.org/DECORATION/1.6.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/DECORATION/1.6.0\n        http://maven.apache.org/xsd/decoration-1.6.0.xsd\">\n    <skin>\n        <groupId>org.apache.maven.skins</groupId>\n        <artifactId>maven-fluido-skin</artifactId>\n        <version>1.11.1</version>\n    </skin>\n    <body>\n        <menu ref=\"parent\" inherit=\"top\" />\n        <menu ref=\"modules\" inherit=\"top\" />\n        <menu ref=\"reports\" inherit=\"top\" />\n    </body>\n    <custom>\n        <fluidoSkin>\n            <topBarEnabled>true</topBarEnabled>\n            <sideBarEnabled>true</sideBarEnabled>\n            <sourceLineNumbersEnabled>true</sourceLineNumbersEnabled>\n            <copyrightClass>pull-right</copyrightClass>\n        </fluidoSkin>\n    </custom>\n</project>\n"
  },
  {
    "path": "webmagic-core/README.md",
    "content": "webmagic-core\n-------\nwebmagic核心部分。只包含爬虫基本模块和基本抽取器。webmagic-core的目标是成为网页爬虫的一个教科书般的实现。"
  },
  {
    "path": "webmagic-core/module_webmagic-core.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project name=\"module_webmagic-core\" default=\"compile.module.webmagic-core\">\n  <dirname property=\"module.webmagic-core.basedir\" file=\"${ant.file.module_webmagic-core}\"/>\n  \n  <property name=\"module.jdk.home.webmagic-core\" value=\"${project.jdk.home}\"/>\n  <property name=\"module.jdk.bin.webmagic-core\" value=\"${project.jdk.bin}\"/>\n  <property name=\"module.jdk.classpath.webmagic-core\" value=\"${project.jdk.classpath}\"/>\n  \n  <property name=\"compiler.args.webmagic-core\" value=\"${compiler.args}\"/>\n  \n  <property name=\"webmagic-core.output.dir\" value=\"${module.webmagic-core.basedir}/target/classes\"/>\n  <property name=\"webmagic-core.testoutput.dir\" value=\"${module.webmagic-core.basedir}/target/test-classes\"/>\n  \n  <path id=\"webmagic-core.module.bootclasspath\">\n    <!-- Paths to be included in compilation bootclasspath -->\n  </path>\n  \n  <path id=\"webmagic-core.module.production.classpath\">\n    <path refid=\"${module.jdk.classpath.webmagic-core}\"/>\n    <path refid=\"library.maven:_org.apache.httpcomponents:httpclient:4.2.4.classpath\"/>\n    <path refid=\"library.maven:_org.apache.httpcomponents:httpcore:4.2.4.classpath\"/>\n    <path refid=\"library.maven:_commons-logging:commons-logging:1.1.1.classpath\"/>\n    <path refid=\"library.maven:_commons-codec:commons-codec:1.6.classpath\"/>\n    <path refid=\"library.maven:_com.google.guava:guava:13.0.1.classpath\"/>\n    <path refid=\"library.maven:_org.apache.commons:commons-lang3:3.1.classpath\"/>\n    <path refid=\"library.maven:_log4j:log4j:1.2.17.classpath\"/>\n    <path refid=\"library.maven:_commons-collections:commons-collections:3.2.1.classpath\"/>\n    <path refid=\"library.maven:_net.sourceforge.htmlcleaner:htmlcleaner:2.4.classpath\"/>\n    <path refid=\"library.maven:_org.jdom:jdom2:2.0.4.classpath\"/>\n    <path refid=\"library.maven:_commons-io:commons-io:1.3.2.classpath\"/>\n  </path>\n  \n  <path id=\"webmagic-core.runtime.production.module.classpath\">\n    <pathelement location=\"${webmagic-core.output.dir}\"/>\n    <path refid=\"library.maven:_org.apache.httpcomponents:httpclient:4.2.4.classpath\"/>\n    <path refid=\"library.maven:_org.apache.httpcomponents:httpcore:4.2.4.classpath\"/>\n    <path refid=\"library.maven:_commons-logging:commons-logging:1.1.1.classpath\"/>\n    <path refid=\"library.maven:_commons-codec:commons-codec:1.6.classpath\"/>\n    <path refid=\"library.maven:_com.google.guava:guava:13.0.1.classpath\"/>\n    <path refid=\"library.maven:_org.apache.commons:commons-lang3:3.1.classpath\"/>\n    <path refid=\"library.maven:_log4j:log4j:1.2.17.classpath\"/>\n    <path refid=\"library.maven:_commons-collections:commons-collections:3.2.1.classpath\"/>\n    <path refid=\"library.maven:_net.sourceforge.htmlcleaner:htmlcleaner:2.4.classpath\"/>\n    <path refid=\"library.maven:_org.jdom:jdom2:2.0.4.classpath\"/>\n    <path refid=\"library.maven:_commons-io:commons-io:1.3.2.classpath\"/>\n  </path>\n  \n  <path id=\"webmagic-core.module.classpath\">\n    <path refid=\"${module.jdk.classpath.webmagic-core}\"/>\n    <pathelement location=\"${webmagic-core.output.dir}\"/>\n    <path refid=\"library.maven:_org.apache.httpcomponents:httpclient:4.2.4.classpath\"/>\n    <path refid=\"library.maven:_org.apache.httpcomponents:httpcore:4.2.4.classpath\"/>\n    <path refid=\"library.maven:_commons-logging:commons-logging:1.1.1.classpath\"/>\n    <path refid=\"library.maven:_commons-codec:commons-codec:1.6.classpath\"/>\n    <path refid=\"library.maven:_junit:junit:4.7.classpath\"/>\n    <path refid=\"library.maven:_com.google.guava:guava:13.0.1.classpath\"/>\n    <path refid=\"library.maven:_org.apache.commons:commons-lang3:3.1.classpath\"/>\n    <path refid=\"library.maven:_log4j:log4j:1.2.17.classpath\"/>\n    <path refid=\"library.maven:_commons-collections:commons-collections:3.2.1.classpath\"/>\n    <path refid=\"library.maven:_net.sourceforge.htmlcleaner:htmlcleaner:2.4.classpath\"/>\n    <path refid=\"library.maven:_org.jdom:jdom2:2.0.4.classpath\"/>\n    <path refid=\"library.maven:_commons-io:commons-io:1.3.2.classpath\"/>\n  </path>\n  \n  <path id=\"webmagic-core.runtime.module.classpath\">\n    <pathelement location=\"${webmagic-core.testoutput.dir}\"/>\n    <pathelement location=\"${webmagic-core.output.dir}\"/>\n    <path refid=\"library.maven:_org.apache.httpcomponents:httpclient:4.2.4.classpath\"/>\n    <path refid=\"library.maven:_org.apache.httpcomponents:httpcore:4.2.4.classpath\"/>\n    <path refid=\"library.maven:_commons-logging:commons-logging:1.1.1.classpath\"/>\n    <path refid=\"library.maven:_commons-codec:commons-codec:1.6.classpath\"/>\n    <path refid=\"library.maven:_junit:junit:4.7.classpath\"/>\n    <path refid=\"library.maven:_com.google.guava:guava:13.0.1.classpath\"/>\n    <path refid=\"library.maven:_org.apache.commons:commons-lang3:3.1.classpath\"/>\n    <path refid=\"library.maven:_log4j:log4j:1.2.17.classpath\"/>\n    <path refid=\"library.maven:_commons-collections:commons-collections:3.2.1.classpath\"/>\n    <path refid=\"library.maven:_net.sourceforge.htmlcleaner:htmlcleaner:2.4.classpath\"/>\n    <path refid=\"library.maven:_org.jdom:jdom2:2.0.4.classpath\"/>\n    <path refid=\"library.maven:_commons-io:commons-io:1.3.2.classpath\"/>\n  </path>\n  \n  \n  <patternset id=\"excluded.from.module.webmagic-core\">\n    <patternset refid=\"ignored.files\"/>\n  </patternset>\n  \n  <patternset id=\"excluded.from.compilation.webmagic-core\">\n    <patternset refid=\"excluded.from.module.webmagic-core\"/>\n  </patternset>\n  \n  <path id=\"webmagic-core.module.sourcepath\">\n    <dirset dir=\"${module.webmagic-core.basedir}\">\n      <include name=\"src/main/java\"/>\n      <include name=\"src/main/resources\"/>\n    </dirset>\n  </path>\n  \n  <path id=\"webmagic-core.module.test.sourcepath\">\n    <dirset dir=\"${module.webmagic-core.basedir}\">\n      <include name=\"src/test/java\"/>\n      <include name=\"src/test/resources\"/>\n    </dirset>\n  </path>\n  \n  \n  <target name=\"compile.module.webmagic-core\" depends=\"compile.module.webmagic-core.production,compile.module.webmagic-core.tests\" description=\"Compile module webmagic-core\"/>\n  \n  <target name=\"compile.module.webmagic-core.production\" depends=\"register.custom.compilers\" description=\"Compile module webmagic-core; production classes\">\n    <mkdir dir=\"${webmagic-core.output.dir}\"/>\n    <javac2 destdir=\"${webmagic-core.output.dir}\" debug=\"${compiler.debug}\" nowarn=\"${compiler.generate.no.warnings}\" memorymaximumsize=\"${compiler.max.memory}\" fork=\"true\" executable=\"${module.jdk.bin.webmagic-core}/javac\">\n      <compilerarg line=\"${compiler.args.webmagic-core}\"/>\n      <bootclasspath refid=\"webmagic-core.module.bootclasspath\"/>\n      <classpath refid=\"webmagic-core.module.production.classpath\"/>\n      <src refid=\"webmagic-core.module.sourcepath\"/>\n      <patternset refid=\"excluded.from.compilation.webmagic-core\"/>\n    </javac2>\n    \n    <copy todir=\"${webmagic-core.output.dir}\">\n      <fileset dir=\"${module.webmagic-core.basedir}/src/main/java\">\n        <patternset refid=\"compiler.resources\"/>\n        <type type=\"file\"/>\n      </fileset>\n      <fileset dir=\"${module.webmagic-core.basedir}/src/main/resources\">\n        <patternset refid=\"compiler.resources\"/>\n        <type type=\"file\"/>\n      </fileset>\n    </copy>\n  </target>\n  \n  <target name=\"compile.module.webmagic-core.tests\" depends=\"register.custom.compilers,compile.module.webmagic-core.production\" description=\"compile module webmagic-core; test classes\" unless=\"skip.tests\">\n    <mkdir dir=\"${webmagic-core.testoutput.dir}\"/>\n    <javac2 destdir=\"${webmagic-core.testoutput.dir}\" debug=\"${compiler.debug}\" nowarn=\"${compiler.generate.no.warnings}\" memorymaximumsize=\"${compiler.max.memory}\" fork=\"true\" executable=\"${module.jdk.bin.webmagic-core}/javac\">\n      <compilerarg line=\"${compiler.args.webmagic-core}\"/>\n      <bootclasspath refid=\"webmagic-core.module.bootclasspath\"/>\n      <classpath refid=\"webmagic-core.module.classpath\"/>\n      <src refid=\"webmagic-core.module.test.sourcepath\"/>\n      <patternset refid=\"excluded.from.compilation.webmagic-core\"/>\n    </javac2>\n    \n    <copy todir=\"${webmagic-core.testoutput.dir}\">\n      <fileset dir=\"${module.webmagic-core.basedir}/src/test/java\">\n        <patternset refid=\"compiler.resources\"/>\n        <type type=\"file\"/>\n      </fileset>\n      <fileset dir=\"${module.webmagic-core.basedir}/src/test/resources\">\n        <patternset refid=\"compiler.resources\"/>\n        <type type=\"file\"/>\n      </fileset>\n    </copy>\n  </target>\n  \n  <target name=\"clean.module.webmagic-core\" description=\"cleanup module\">\n    <delete dir=\"${webmagic-core.output.dir}\"/>\n    <delete dir=\"${webmagic-core.testoutput.dir}\"/>\n  </target>\n</project>"
  },
  {
    "path": "webmagic-core/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n    xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://maven.apache.org/POM/4.0.0\n        http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>us.codecraft</groupId>\n        <artifactId>webmagic</artifactId>\n        <version>1.0.4-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>webmagic-core</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.apache.httpcomponents</groupId>\n            <artifactId>httpclient</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-lang3</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>us.codecraft</groupId>\n            <artifactId>xsoup</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.github.dreamhead</groupId>\n            <artifactId>moco-core</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-all</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-collections4</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.assertj</groupId>\n            <artifactId>assertj-core</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>commons-io</groupId>\n            <artifactId>commons-io</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.jayway.jsonpath</groupId>\n            <artifactId>json-path</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>fastjson</artifactId>\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/Page.java",
    "content": "package us.codecraft.webmagic;\n\nimport org.apache.commons.lang3.StringUtils;\nimport us.codecraft.webmagic.selector.Html;\nimport us.codecraft.webmagic.selector.Json;\nimport us.codecraft.webmagic.selector.Selectable;\nimport us.codecraft.webmagic.utils.UrlUtils;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Object storing extracted result and urls to fetch.<br>\n * Not thread safe.<br>\n * Main method：                                               <br>\n * {@link #getUrl()} get url of current page                   <br>\n * {@link #getHtml()}  get content of current page                 <br>\n * {@link #putField(String, Object)}  save extracted result            <br>\n * {@link #getResultItems()} get extract results to be used in {@link us.codecraft.webmagic.pipeline.Pipeline}<br>\n * {@link #addTargetRequests(Iterable)} {@link #addTargetRequest(String)} add urls to fetch                 <br>\n *\n * @author code4crafter@gmail.com <br>\n * @see us.codecraft.webmagic.downloader.Downloader\n * @see us.codecraft.webmagic.processor.PageProcessor\n * @since 0.1.0\n */\npublic class Page {\n\n    private Request request;\n\n    private ResultItems resultItems = new ResultItems();\n\n    private Html html;\n\n    private Json json;\n\n    private String rawText;\n\n    private Selectable url;\n\n    private Map<String,List<String>> headers;\n\n    private int statusCode;\n\n    private boolean downloadSuccess;\n\n    private byte[] bytes;\n\n    private List<Request> targetRequests = new ArrayList<>();\n\n    private String charset;\n\n    /**\n     * Returns a {@link Page} with {@link #downloadSuccess} is {@code true},\n     * and {@link #request} is specified.\n     *\n     * @param request the request.\n     * @since 1.0.2\n     */\n    public static Page ofSuccess(Request request) {\n        return new Page(request, true);\n    }\n\n    /**\n     * Returns a {@link Page} with {@link #downloadSuccess} is {@code true},\n     * and {@link #request} is specified.\n     *\n     * @param request the request.\n     * @since 1.0.2\n     */\n    public static Page ofFailure(Request request) {\n        return new Page(request, false);\n    }\n\n    public Page() {\n    }\n\n    /**\n     * Constructs a {@link Page} with {@link #request}\n     * and {@link #downloadSuccess} specified.\n     *\n     * @param request the request.\n     * @param downloadSuccess the download success flag.\n     * @since 1.0.2\n     */\n    private Page(Request request, boolean downloadSuccess) {\n        this.request = request;\n        this.downloadSuccess = downloadSuccess;\n    }\n\n    /**\n     * Returns a {@link Page} with {@link #downloadSuccess} is {@code false}.\n     *\n     * @return the page.\n     * @deprecated Use {@link #fail(Request)} instead.\n     */\n    @Deprecated\n    public static Page fail() {\n        return fail(null);\n    }\n\n    /**\n     * Returns a {@link Page} with {@link #downloadSuccess} is {@code false},\n     * and {@link #request} is specified.\n     *\n     * @param request the {@link Request}.\n     * @return the page.\n     * @since 0.10.0\n     * @deprecated Use {@link #ofFailure(Request)} instead.\n     */\n    @Deprecated(since = \"1.0.2\", forRemoval = true)\n    public static Page fail(Request request){\n        Page page = new Page();\n        page.setRequest(request);\n        page.setDownloadSuccess(false);\n        return page;\n    }\n\n    public Page setSkip(boolean skip) {\n        resultItems.setSkip(skip);\n        return this;\n\n    }\n\n    /**\n     * store extract results\n     *\n     * @param key key\n     * @param field field\n     */\n    public void putField(String key, Object field) {\n        resultItems.put(key, field);\n    }\n\n    /**\n     * get html content of page\n     *\n     * @return html\n     */\n    public Html getHtml() {\n        if (html == null) {\n            html = new Html(rawText, request.getUrl());\n        }\n        return html;\n    }\n\n    /**\n     * get json content of page\n     *\n     * @return json\n     * @since 0.5.0\n     */\n    public Json getJson() {\n        if (json == null) {\n            json = new Json(rawText);\n        }\n        return json;\n    }\n\n    /**\n     * @param html html\n     * @deprecated since 0.4.0\n     * The html is parse just when first time of calling {@link #getHtml()}, so use {@link #setRawText(String)} instead.\n     */\n    @Deprecated\n\tpublic void setHtml(Html html) {\n        this.html = html;\n    }\n\n    public List<Request> getTargetRequests() {\n        return targetRequests;\n    }\n\n    /**\n     * add urls to fetch\n     *\n     * @param requests requests\n     */\n    public void addTargetRequests(Iterable<String> requests) {\n    \taddTargetRequests(requests, 0); // Default priority is 0\n    }\n\n    /**\n     * add urls to fetch\n     *\n     * @param requests requests\n     * @param priority priority\n     */\n    public void addTargetRequests(Iterable<String> requests, long priority) {\n    \tif(requests == null) {\n    \t\treturn;\n    \t}\n    \t\n        for (String req : requests) {\n        \taddRequestIfValid(req, priority);\n        }\n    }\n    \n    /**\n     * Helper method to add a request if it's valid.\n     *\n     * @param url      URL to add\n     * @param priority Priority for the URL\n     */\n    private void addRequestIfValid(String url, long priority) {\n        if (StringUtils.isBlank(url) || url.equals(\"#\") || url.startsWith(\"javascript:\")) {\n            return;\n        }\n\n        String canonicalizedUrl = UrlUtils.canonicalizeUrl(url, this.url.toString());\n        Request req = new Request(canonicalizedUrl);\n        if(priority > 0) {\n            req.setPriority(priority);\n        }\n        targetRequests.add(req);\n    }\n\n    /**\n     * add url to fetch\n     *\n     * @param requestString requestString\n     */\n    public void addTargetRequest(String requestString) {\n        if (StringUtils.isBlank(requestString) || requestString.equals(\"#\")) {\n            return;\n        }\n        requestString = UrlUtils.canonicalizeUrl(requestString, url.toString());\n        targetRequests.add(new Request(requestString));\n    }\n\n    /**\n     * add requests to fetch\n     *\n     * @param request request\n     */\n    public void addTargetRequest(Request request) {\n        targetRequests.add(request);\n    }\n\n    /**\n     * get url of current page\n     *\n     * @return url of current page\n     */\n    public Selectable getUrl() {\n        return url;\n    }\n\n    public void setUrl(Selectable url) {\n        this.url = url;\n    }\n\n    /**\n     * get request of current page\n     *\n     * @return request\n     */\n    public Request getRequest() {\n        return request;\n    }\n\n    public void setRequest(Request request) {\n        this.request = request;\n        this.resultItems.setRequest(request);\n    }\n\n    public ResultItems getResultItems() {\n        return resultItems;\n    }\n\n    public int getStatusCode() {\n        return statusCode;\n    }\n\n    public void setStatusCode(int statusCode) {\n        this.statusCode = statusCode;\n    }\n\n    public String getRawText() {\n        return rawText;\n    }\n\n    public Page setRawText(String rawText) {\n        this.rawText = rawText;\n        return this;\n    }\n\n    public Map<String, List<String>> getHeaders() {\n        return headers;\n    }\n\n    public void setHeaders(Map<String, List<String>> headers) {\n        this.headers = headers;\n    }\n\n    public boolean isDownloadSuccess() {\n        return downloadSuccess;\n    }\n\n    public void setDownloadSuccess(boolean downloadSuccess) {\n        this.downloadSuccess = downloadSuccess;\n    }\n\n    public byte[] getBytes() {\n        return bytes;\n    }\n\n    public void setBytes(byte[] bytes) {\n        this.bytes = bytes;\n    }\n\n    public String getCharset() {\n        return charset;\n    }\n\n    public void setCharset(String charset) {\n        this.charset = charset;\n    }\n\n    @Override\n    public String toString() {\n        return \"Page{\" +\n                \"request=\" + request +\n                \", resultItems=\" + resultItems +\n                \", html=\" + html +\n                \", json=\" + json +\n                \", rawText='\" + rawText + '\\'' +\n                \", url=\" + url +\n                \", headers=\" + headers +\n                \", statusCode=\" + statusCode +\n                \", downloadSuccess=\" + downloadSuccess +\n                \", targetRequests=\" + targetRequests +\n                \", charset='\" + charset + '\\'' +\n                \", bytes=\" + Arrays.toString(bytes) +\n                '}';\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/Request.java",
    "content": "package us.codecraft.webmagic;\n\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport us.codecraft.webmagic.downloader.Downloader;\nimport us.codecraft.webmagic.model.HttpRequestBody;\nimport us.codecraft.webmagic.utils.Experimental;\n\n/**\n * Object contains url to crawl.<br>\n * It contains some additional information.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class Request implements Serializable {\n\n    private static final long serialVersionUID = 2062192774891352043L;\n\n    public static final String CYCLE_TRIED_TIMES = \"_cycle_tried_times\";\n\n    private String url;\n\n    private String method;\n\n    private HttpRequestBody requestBody;\n\n    /**\n     * this req use this downloader\n     */\n    private Downloader downloader;\n\n    /**\n     * Store additional information in extras.\n     */\n    private Map<String, Object> extras = new HashMap<>();\n\n    /**\n     * cookies for current url, if not set use Site's cookies\n     */\n    private Map<String, String> cookies = new HashMap<String, String>();\n\n    private Map<String, String> headers = new HashMap<String, String>();\n\n    /**\n     * Priority of the request.<br>\n     * The bigger will be processed earlier. <br>\n     * @see us.codecraft.webmagic.scheduler.PriorityScheduler\n     */\n    private long priority;\n\n    /**\n     * When it is set to TRUE, the downloader will not try to parse response body to text.\n     *\n     */\n    private boolean binaryContent = false;\n\n    private String charset;\n\n    public Request() {\n    }\n\n    public Request(String url) {\n        this.url = url;\n    }\n\n    public long getPriority() {\n        return priority;\n    }\n\n    /**\n     * Set the priority of request for sorting.<br>\n     * Need a scheduler supporting priority.<br>\n     * @see us.codecraft.webmagic.scheduler.PriorityScheduler\n     *\n     * @param priority priority\n     * @return this\n     */\n    @Experimental\n    public Request setPriority(long priority) {\n        this.priority = priority;\n        return this;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getExtra(String key) {\n        if (extras == null) {\n            return null;\n        }\n        return (T) extras.get(key);\n    }\n\n    public <T> Request putExtra(String key, T value) {\n        extras.put(key, value);\n        return this;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public Map<String, Object> getExtras() {\n        return Collections.unmodifiableMap(extras);\n    }\n\n    public Request setExtras(Map<String, Object> extras) {\n        this.extras.putAll(extras);\n        return this;\n    }\n\n    public Request setUrl(String url) {\n        this.url = url;\n        return this;\n    }\n\n    /**\n     * The http method of the request. Get for default.\n     * @return httpMethod\n     * @see us.codecraft.webmagic.utils.HttpConstant.Method\n     * @since 0.5.0\n     */\n    public String getMethod() {\n        return method;\n    }\n\n    public Request setMethod(String method) {\n        this.method = method;\n        return this;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = url != null ? url.hashCode() : 0;\n        result = 31 * result + (method != null ? method.hashCode() : 0);\n        return result;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        Request request = (Request) o;\n\n        if (url != null ? !url.equals(request.url) : request.url != null) return false;\n        return method != null ? method.equals(request.method) : request.method == null;\n    }\n\n    public Request addCookie(String name, String value) {\n        cookies.put(name, value);\n        return this;\n    }\n\n    public Request addHeader(String name, String value) {\n        headers.put(name, value);\n        return this;\n    }\n\n    public Map<String, String> getCookies() {\n        return cookies;\n    }\n\n    public Map<String, String> getHeaders() {\n        return headers;\n    }\n\n    public HttpRequestBody getRequestBody() {\n        return requestBody;\n    }\n\n    public void setRequestBody(HttpRequestBody requestBody) {\n        this.requestBody = requestBody;\n    }\n\n    public boolean isBinaryContent() {\n        return binaryContent;\n    }\n\n    public Downloader getDownloader() {\n        return downloader;\n    }\n\n    public void setDownloader(Downloader downloader) {\n        this.downloader = downloader;\n    }\n\n    public Request setBinaryContent(boolean binaryContent) {\n        this.binaryContent = binaryContent;\n        return this;\n    }\n\n    public String getCharset() {\n        return charset;\n    }\n\n    public Request setCharset(String charset) {\n        this.charset = charset;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return \"Request{\" +\n                \"url='\" + url + '\\'' +\n                \", method='\" + method + '\\'' +\n                \", extras=\" + extras +\n                \", priority=\" + priority +\n                \", headers=\" + headers +\n                \", cookies=\"+ cookies+\n                '}';\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/ResultItems.java",
    "content": "package us.codecraft.webmagic;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * Object contains extract results.<br>\n * It is contained in Page and will be processed in pipeline.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n * @see Page\n * @see us.codecraft.webmagic.pipeline.Pipeline\n */\npublic class ResultItems {\n\n    private Map<String, Object> fields = new LinkedHashMap<String, Object>();\n\n    private Request request;\n\n    private boolean skip;\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T get(String key) {\n        Object o = fields.get(key);\n        if (o == null) {\n            return null;\n        }\n        return (T) fields.get(key);\n    }\n\n    public Map<String, Object> getAll() {\n        return fields;\n    }\n\n    public <T> ResultItems put(String key, T value) {\n        fields.put(key, value);\n        return this;\n    }\n\n    public Request getRequest() {\n        return request;\n    }\n\n    public ResultItems setRequest(Request request) {\n        this.request = request;\n        return this;\n    }\n\n    /**\n     * Whether to skip the result.<br>\n     * Result which is skipped will not be processed by Pipeline.\n     *\n     * @return whether to skip the result\n     */\n    public boolean isSkip() {\n        return skip;\n    }\n\n\n    /**\n     * Set whether to skip the result.<br>\n     * Result which is skipped will not be processed by Pipeline.\n     *\n     * @param skip whether to skip the result\n     * @return this\n     */\n    public ResultItems setSkip(boolean skip) {\n        this.skip = skip;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return \"ResultItems{\" +\n                \"fields=\" + fields +\n                \", request=\" + request +\n                \", skip=\" + skip +\n                '}';\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/Site.java",
    "content": "package us.codecraft.webmagic;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\n\nimport us.codecraft.webmagic.utils.HttpConstant;\n\n/**\n * Object contains setting for crawler.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @see us.codecraft.webmagic.processor.PageProcessor\n * @since 0.1.0\n */\npublic class Site {\n\n    private String domain;\n\n    private String userAgent;\n\n    private Map<String, String> defaultCookies = new LinkedHashMap<String, String>();\n\n    private Map<String, Map<String, String>> cookies = new HashMap<String, Map<String, String>>();\n\n    private String charset;\n\n    private String defaultCharset;\n\n    private int sleepTime = 5000;\n\n    private int retryTimes = 0;\n\n    private int cycleRetryTimes = 0;\n\n    private int retrySleepTime = 1000;\n\n    private int timeOut = 5000;\n\n    private static final Set<Integer> DEFAULT_STATUS_CODE_SET = new HashSet<Integer>();\n\n    private Set<Integer> acceptStatCode = DEFAULT_STATUS_CODE_SET;\n\n    private Map<String, String> headers = new HashMap<String, String>();\n\n    private boolean useGzip = true;\n\n    private boolean disableCookieManagement = false;\n\n    static {\n        DEFAULT_STATUS_CODE_SET.add(HttpConstant.StatusCode.CODE_200);\n    }\n\n    /**\n     * new a Site\n     *\n     * @return new site\n     */\n    public static Site me() {\n        return new Site();\n    }\n\n    /**\n     * Add a cookie with domain {@link #getDomain()}\n     *\n     * @param name name\n     * @param value value\n     * @return this\n     */\n    public Site addCookie(String name, String value) {\n        defaultCookies.put(name, value);\n        return this;\n    }\n\n    /**\n     * Add a cookie with specific domain.\n     *\n     * @param domain domain\n     * @param name name\n     * @param value value\n     * @return this\n     */\n    public Site addCookie(String domain, String name, String value) {\n        if (!cookies.containsKey(domain)){\n            cookies.put(domain,new HashMap<String, String>());\n        }\n        cookies.get(domain).put(name, value);\n        return this;\n    }\n\n    /**\n     * set user agent\n     *\n     * @param userAgent userAgent\n     * @return this\n     */\n    public Site setUserAgent(String userAgent) {\n        this.userAgent = userAgent;\n        return this;\n    }\n\n    /**\n     * get cookies\n     *\n     * @return get cookies\n     */\n    public Map<String, String> getCookies() {\n        return defaultCookies;\n    }\n\n    /**\n     * get cookies of all domains\n     *\n     * @return get cookies\n     */\n    public Map<String,Map<String, String>> getAllCookies() {\n        return cookies;\n    }\n\n    /**\n     * get user agent\n     *\n     * @return user agent\n     */\n    public String getUserAgent() {\n        return userAgent;\n    }\n\n    /**\n     * get domain\n     *\n     * @return get domain\n     */\n    public String getDomain() {\n        return domain;\n    }\n\n    /**\n     * set the domain of site.\n     *\n     * @param domain domain\n     * @return this\n     */\n    public Site setDomain(String domain) {\n        this.domain = domain;\n        return this;\n    }\n\n    /**\n     * Set charset of page manually.<br>\n     * When charset is not set or set to null, it can be auto detected by Http header.\n     *\n     * @param charset charset\n     * @return this\n     */\n    public Site setCharset(String charset) {\n        this.charset = charset;\n        return this;\n    }\n\n    /**\n     * get charset set manually\n     *\n     * @return charset\n     */\n    public String getCharset() {\n        return charset;\n    }\n\n    /**\n     * Set default charset of page.\n     *\n     * When charset detect failed, use this default charset.\n     *\n     * @param defaultCharset the default charset\n     * @return this\n     * @since 0.9.0\n     */\n    public Site setDefaultCharset(String defaultCharset) {\n        this.defaultCharset = defaultCharset;\n        return this;\n    }\n\n    /**\n     * The default charset if charset detected failed.\n     *\n     * @return the defulat charset\n     * @since 0.9.0\n     */\n    public String getDefaultCharset() {\n        return defaultCharset;\n    }\n\n    public int getTimeOut() {\n        return timeOut;\n    }\n\n    /**\n     * set timeout for downloader in ms\n     *\n     * @param timeOut timeOut\n     * @return this\n     */\n    public Site setTimeOut(int timeOut) {\n        this.timeOut = timeOut;\n        return this;\n    }\n\n    /**\n     * Set acceptStatCode.<br>\n     * When status code of http response is in acceptStatCodes, it will be processed.<br>\n     * {200} by default.<br>\n     * It is not necessarily to be set.<br>\n     *\n     * @param acceptStatCode acceptStatCode\n     * @return this\n     */\n    public Site setAcceptStatCode(Set<Integer> acceptStatCode) {\n        this.acceptStatCode = acceptStatCode;\n        return this;\n    }\n\n    /**\n     * get acceptStatCode\n     *\n     * @return acceptStatCode\n     */\n    public Set<Integer> getAcceptStatCode() {\n        return acceptStatCode;\n    }\n\n    /**\n     * Set the interval between the processing of two pages.<br>\n     * Time unit is milliseconds.<br>\n     *\n     * @param sleepTime sleepTime\n     * @return this\n     */\n    public Site setSleepTime(int sleepTime) {\n        this.sleepTime = sleepTime;\n        return this;\n    }\n\n    /**\n     * Get the interval between the processing of two pages.<br>\n     * Time unit is milliseconds.<br>\n     *\n     * @return the interval between the processing of two pages,\n     */\n    public int getSleepTime() {\n        return sleepTime;\n    }\n\n    /**\n     * Get retry times immediately when download fail, 0 by default.<br>\n     *\n     * @return retry times when download fail\n     */\n    public int getRetryTimes() {\n        return retryTimes;\n    }\n\n    public Map<String, String> getHeaders() {\n        return headers;\n    }\n\n    /**\n     * Put an Http header for downloader. <br>\n     * Use {@link #addCookie(String, String)} for cookie and {@link #setUserAgent(String)} for user-agent. <br>\n     *\n     * @param key   key of http header, there are some keys constant in {@link HttpConstant.Header}\n     * @param value value of header\n     * @return this\n     */\n    public Site addHeader(String key, String value) {\n        headers.put(key, value);\n        return this;\n    }\n\n    /**\n     * Set retry times when download fail, 0 by default.<br>\n     *\n     * @param retryTimes retryTimes\n     * @return this\n     */\n    public Site setRetryTimes(int retryTimes) {\n        this.retryTimes = retryTimes;\n        return this;\n    }\n\n    /**\n     * When cycleRetryTimes is more than 0, it will add back to scheduler and try download again. <br>\n     *\n     * @return retry times when download fail\n     */\n    public int getCycleRetryTimes() {\n        return cycleRetryTimes;\n    }\n\n    /**\n     * Set cycleRetryTimes times when download fail, 0 by default. <br>\n     *\n     * @param cycleRetryTimes cycleRetryTimes\n     * @return this\n     */\n    public Site setCycleRetryTimes(int cycleRetryTimes) {\n        this.cycleRetryTimes = cycleRetryTimes;\n        return this;\n    }\n\n    public boolean isUseGzip() {\n        return useGzip;\n    }\n\n    public int getRetrySleepTime() {\n        return retrySleepTime;\n    }\n\n    /**\n     * Set retry sleep times when download fail, 1000 by default. <br>\n     *\n     * @param retrySleepTime retrySleepTime\n     * @return this\n     */\n    public Site setRetrySleepTime(int retrySleepTime) {\n        this.retrySleepTime = retrySleepTime;\n        return this;\n    }\n\n    /**\n     * Whether use gzip. <br>\n     * Default is true, you can set it to false to disable gzip.\n     *\n     * @param useGzip useGzip\n     * @return this\n     */\n    public Site setUseGzip(boolean useGzip) {\n        this.useGzip = useGzip;\n        return this;\n    }\n\n    public boolean isDisableCookieManagement() {\n        return disableCookieManagement;\n    }\n\n    /**\n     * Downloader is supposed to store response cookie.\n     * Disable it to ignore all cookie fields and stay clean.\n     * Warning: Set cookie will still NOT work if disableCookieManagement is true.\n     * @param disableCookieManagement disableCookieManagement\n     * @return this\n     */\n    public Site setDisableCookieManagement(boolean disableCookieManagement) {\n        this.disableCookieManagement = disableCookieManagement;\n        return this;\n    }\n\n    public Task toTask() {\n        return new Task() {\n            @Override\n            public String getUUID() {\n                String uuid = Site.this.getDomain();\n                if (uuid == null) {\n                    uuid = UUID.randomUUID().toString();\n                }\n                return uuid;\n            }\n\n            @Override\n            public Site getSite() {\n                return Site.this;\n            }\n        };\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        Site site = (Site) o;\n\n        if (cycleRetryTimes != site.cycleRetryTimes) return false;\n        if (retryTimes != site.retryTimes) return false;\n        if (sleepTime != site.sleepTime) return false;\n        if (timeOut != site.timeOut) return false;\n        if (acceptStatCode != null ? !acceptStatCode.equals(site.acceptStatCode) : site.acceptStatCode != null)\n            return false;\n        if (charset != null ? !charset.equals(site.charset) : site.charset != null) return false;\n        if (defaultCookies != null ? !defaultCookies.equals(site.defaultCookies) : site.defaultCookies != null)\n            return false;\n        if (domain != null ? !domain.equals(site.domain) : site.domain != null) return false;\n        if (headers != null ? !headers.equals(site.headers) : site.headers != null) return false;\n        if (userAgent != null ? !userAgent.equals(site.userAgent) : site.userAgent != null) return false;\n\n        return true;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = domain != null ? domain.hashCode() : 0;\n        result = 31 * result + (userAgent != null ? userAgent.hashCode() : 0);\n        result = 31 * result + (defaultCookies != null ? defaultCookies.hashCode() : 0);\n        result = 31 * result + (charset != null ? charset.hashCode() : 0);\n        result = 31 * result + sleepTime;\n        result = 31 * result + retryTimes;\n        result = 31 * result + cycleRetryTimes;\n        result = 31 * result + timeOut;\n        result = 31 * result + (acceptStatCode != null ? acceptStatCode.hashCode() : 0);\n        result = 31 * result + (headers != null ? headers.hashCode() : 0);\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return \"Site{\" +\n                \"domain='\" + domain + '\\'' +\n                \", userAgent='\" + userAgent + '\\'' +\n                \", cookies=\" + defaultCookies +\n                \", charset='\" + charset + '\\'' +\n                \", sleepTime=\" + sleepTime +\n                \", retryTimes=\" + retryTimes +\n                \", cycleRetryTimes=\" + cycleRetryTimes +\n                \", timeOut=\" + timeOut +\n                \", acceptStatCode=\" + acceptStatCode +\n                \", headers=\" + headers +\n                '}';\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/Spider.java",
    "content": "package us.codecraft.webmagic;\n\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.SerializationUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.downloader.Downloader;\nimport us.codecraft.webmagic.downloader.HttpClientDownloader;\nimport us.codecraft.webmagic.pipeline.CollectorPipeline;\nimport us.codecraft.webmagic.pipeline.ConsolePipeline;\nimport us.codecraft.webmagic.pipeline.Pipeline;\nimport us.codecraft.webmagic.pipeline.ResultItemsCollectorPipeline;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.scheduler.QueueScheduler;\nimport us.codecraft.webmagic.scheduler.Scheduler;\nimport us.codecraft.webmagic.thread.CountableThreadPool;\nimport us.codecraft.webmagic.utils.UrlUtils;\nimport us.codecraft.webmagic.utils.WMCollections;\n\n/**\n * Entrance of a crawler.<br>\n * A spider contains four modules: Downloader, Scheduler, PageProcessor and\n * Pipeline.<br>\n * Every module is a field of Spider. <br>\n * The modules are defined in interface. <br>\n * You can customize a spider with various implementations of them. <br>\n * Examples: <br>\n * <br>\n * A simple crawler: <br>\n * Spider.create(new SimplePageProcessor(\"http://my.oschina.net/\",\n * \"http://my.oschina.net/*blog/*\")).run();<br>\n * <br>\n * Store results to files by FilePipeline: <br>\n * Spider.create(new SimplePageProcessor(\"http://my.oschina.net/\",\n * \"http://my.oschina.net/*blog/*\")) <br>\n * .pipeline(new FilePipeline(\"/data/temp/webmagic/\")).run(); <br>\n * <br>\n * Use FileCacheQueueScheduler to store urls and cursor in files, so that a\n * Spider can resume the status when shutdown. <br>\n * Spider.create(new SimplePageProcessor(\"http://my.oschina.net/\",\n * \"http://my.oschina.net/*blog/*\")) <br>\n * .scheduler(new FileCacheQueueScheduler(\"/data/temp/webmagic/cache/\")).run(); <br>\n *\n * @author code4crafter@gmail.com <br>\n * @see Downloader\n * @see Scheduler\n * @see PageProcessor\n * @see Pipeline\n * @since 0.1.0\n */\npublic class Spider implements Runnable, Task {\n\n    protected Downloader downloader;\n\n    protected List<Pipeline> pipelines = new ArrayList<Pipeline>();\n\n    protected PageProcessor pageProcessor;\n\n    protected List<Request> startRequests;\n\n    protected Site site;\n\n    protected String uuid;\n    \n    protected SpiderScheduler scheduler;\n    \n    protected Logger logger = LoggerFactory.getLogger(getClass());\n\n    protected CountableThreadPool threadPool;\n\n    protected ExecutorService executorService;\n\n    protected int threadNum = 1;\n\n    protected AtomicInteger stat = new AtomicInteger(STAT_INIT);\n\n    protected volatile boolean exitWhenComplete = true;\n\n    protected final static int STAT_INIT = 0;\n\n    protected final static int STAT_RUNNING = 1;\n\n    protected final static int STAT_STOPPED = 2;\n\n    protected boolean spawnUrl = true;\n\n    protected boolean destroyWhenExit = true;\n\n    private List<SpiderListener> spiderListeners;\n\n    private final AtomicLong pageCount = new AtomicLong(0);\n\n    private Date startTime;\n\n    private long emptySleepTime = 30000;\n\n    /**\n     * create a spider with pageProcessor.\n     *\n     * @param pageProcessor pageProcessor\n     * @return new spider\n     * @see PageProcessor\n     */\n    public static Spider create(PageProcessor pageProcessor) {\n        return new Spider(pageProcessor);\n    }\n\n    /**\n     * create a spider with pageProcessor.\n     *\n     * @param pageProcessor pageProcessor\n     */\n    public Spider(PageProcessor pageProcessor) {\n        this.pageProcessor = pageProcessor;\n        this.site = pageProcessor.getSite();\n        this.scheduler = new SpiderScheduler(new QueueScheduler());\n    }\n\n    /**\n     * Set startUrls of Spider.<br>\n     * Prior to startUrls of Site.\n     *\n     * @param startUrls startUrls\n     * @return this\n     */\n    public Spider startUrls(List<String> startUrls) {\n        checkIfRunning();\n        this.startRequests = UrlUtils.convertToRequests(startUrls);\n        return this;\n    }\n\n    /**\n     * Set startUrls of Spider.<br>\n     * Prior to startUrls of Site.\n     *\n     * @param startRequests startRequests\n     * @return this\n     */\n    public Spider startRequest(List<Request> startRequests) {\n        checkIfRunning();\n        this.startRequests = startRequests;\n        return this;\n    }\n\n    /**\n     * Set an uuid for spider.<br>\n     * Default uuid is domain of site.<br>\n     *\n     * @param uuid uuid\n     * @return this\n     */\n    public Spider setUUID(String uuid) {\n        this.uuid = uuid;\n        return this;\n    }\n\n    /**\n     * set scheduler for Spider\n     *\n     * @param scheduler scheduler\n     * @return this\n     * @see #setScheduler(us.codecraft.webmagic.scheduler.Scheduler)\n     */\n    @Deprecated\n    public Spider scheduler(Scheduler scheduler) {\n        return setScheduler(scheduler);\n    }\n\n    /**\n     * set scheduler for Spider\n     *\n     * @param updateScheduler scheduler\n     * @return this\n     * @see Scheduler\n     * @since 0.2.1\n     */\n    public Spider setScheduler(Scheduler updateScheduler) {\n        checkIfRunning();\n        Scheduler oldScheduler = scheduler.getScheduler();\n        scheduler.setScheduler(updateScheduler);\n        if (oldScheduler != null) {\n            Request request;\n            while ((request = oldScheduler.poll(this)) != null) {\n                this.scheduler.push(request, this);\n            }\n        }\n        return this;\n    }\n\n    /**\n     * add a pipeline for Spider\n     *\n     * @param pipeline pipeline\n     * @return this\n     * @see #addPipeline(us.codecraft.webmagic.pipeline.Pipeline)\n     * @deprecated\n     */\n    @Deprecated\n    public Spider pipeline(Pipeline pipeline) {\n        return addPipeline(pipeline);\n    }\n\n    /**\n     * add a pipeline for Spider\n     *\n     * @param pipeline pipeline\n     * @return this\n     * @see Pipeline\n     * @since 0.2.1\n     */\n    public Spider addPipeline(Pipeline pipeline) {\n        checkIfRunning();\n        this.pipelines.add(pipeline);\n        return this;\n    }\n\n    /**\n     * set pipelines for Spider\n     *\n     * @param pipelines pipelines\n     * @return this\n     * @see Pipeline\n     * @since 0.4.1\n     */\n    public Spider setPipelines(List<Pipeline> pipelines) {\n        checkIfRunning();\n        this.pipelines = pipelines;\n        return this;\n    }\n\n    /**\n     * clear the pipelines set\n     *\n     * @return this\n     */\n    public Spider clearPipeline() {\n        pipelines = new ArrayList<Pipeline>();\n        return this;\n    }\n\n    /**\n     * set the downloader of spider\n     *\n     * @param downloader downloader\n     * @return this\n     * @see #setDownloader(us.codecraft.webmagic.downloader.Downloader)\n     * @deprecated\n     */\n    @Deprecated\n    public Spider downloader(Downloader downloader) {\n        return setDownloader(downloader);\n    }\n\n    /**\n     * set the downloader of spider\n     *\n     * @param downloader downloader\n     * @return this\n     * @see Downloader\n     */\n    public Spider setDownloader(Downloader downloader) {\n        checkIfRunning();\n        this.downloader = downloader;\n        return this;\n    }\n\n    protected void initComponent() {\n        if (downloader == null) {\n            this.downloader = new HttpClientDownloader();\n        }\n        if (pipelines.isEmpty()) {\n            pipelines.add(new ConsolePipeline());\n        }\n        downloader.setThread(threadNum);\n        if (threadPool == null || threadPool.isShutdown()) {\n            if (executorService != null && !executorService.isShutdown()) {\n                threadPool = new CountableThreadPool(threadNum, executorService);\n            } else {\n                threadPool = new CountableThreadPool(threadNum);\n            }\n        }\n        if (startRequests != null) {\n            for (Request request : startRequests) {\n                addRequest(request);\n            }\n            startRequests.clear();\n        }\n        startTime = new Date();\n    }\n\n    @Override\n    public void run() {\n        checkRunningStat();\n        initComponent();\n        logger.info(\"Spider {} started!\", getUUID());\n        // interrupt won't be necessarily detected\n        while (!Thread.currentThread().isInterrupted() && stat.get() == STAT_RUNNING) {\n            Request poll = scheduler.poll(this);\n            if (poll == null) {\n                if (threadPool.getThreadAlive() == 0) {\n                    //no alive thread anymore , try again\n                    poll = scheduler.poll(this);\n                    if (poll == null) {\n                        if (exitWhenComplete) {\n                            break;\n                        } else {\n                            // wait\n                            try {\n                                Thread.sleep(emptySleepTime);\n                                continue;\n                            } catch (InterruptedException e) {\n                                Thread.currentThread().interrupt();\n                                break;\n                            }\n                        }\n                    }\n                } else {\n                    // wait until new url added，\n                    if (scheduler.waitNewUrl(threadPool, emptySleepTime)) {\n                        // if interrupted\n                        break;\n                    }\n                    continue;\n                }\n            }\n            final Request request = poll;\n            //this may swallow the interruption\n            threadPool.execute(new Runnable() {\n                @Override\n                public void run() {\n                    try {\n                        processRequest(request);\n                        onSuccess(request);\n                    } catch (Exception e) {\n                        onError(request, e);\n                        logger.error(\"process request \" + request + \" error\", e);\n                    } finally {\n                        pageCount.incrementAndGet();\n                        scheduler.signalNewUrl();\n                    }\n                }\n            });\n        }\n        stat.set(STAT_STOPPED);\n        // release some resources\n        if (destroyWhenExit) {\n            close();\n        }\n        logger.info(\"Spider {} closed! {} pages downloaded.\", getUUID(), pageCount.get());\n    }\n\n    /**\n     * @deprecated Use {@link #onError(Request, Exception)} instead.\n     */\n    @Deprecated\n    protected void onError(Request request) {\n    }\n\n    protected void onError(Request request, Exception e) {\n        this.onError(request);\n\n        if (CollectionUtils.isNotEmpty(spiderListeners)) {\n            for (SpiderListener spiderListener : spiderListeners) {\n                spiderListener.onError(request, e);\n            }\n        }\n    }\n\n    protected void onSuccess(Request request) {\n        if (CollectionUtils.isNotEmpty(spiderListeners)) {\n            for (SpiderListener spiderListener : spiderListeners) {\n                spiderListener.onSuccess(request);\n            }\n        }\n    }\n\n    private void checkRunningStat() {\n        while (true) {\n            int statNow = stat.get();\n            if (statNow == STAT_RUNNING) {\n                throw new IllegalStateException(\"Spider is already running!\");\n            }\n            if (stat.compareAndSet(statNow, STAT_RUNNING)) {\n                break;\n            }\n        }\n    }\n\n    public void close() {\n        destroyEach(downloader);\n        destroyEach(pageProcessor);\n        destroyEach(scheduler);\n        for (Pipeline pipeline : pipelines) {\n            destroyEach(pipeline);\n        }\n        threadPool.shutdown();\n    }\n\n    private void destroyEach(Object object) {\n        if (object instanceof Closeable) {\n            try {\n                ((Closeable) object).close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Process specific urls without url discovering.\n     *\n     * @param urls urls to process\n     */\n    public void test(String... urls) {\n        initComponent();\n        if (urls.length > 0) {\n            for (String url : urls) {\n                processRequest(new Request(url));\n            }\n        }\n    }\n\n    private void processRequest(Request request) {\n        Page page;\n        if (null != request.getDownloader()){\n            page = request.getDownloader().download(request,this);\n        }else {\n            page = downloader.download(request, this);\n        }\n        if (page.isDownloadSuccess()){\n            onDownloadSuccess(request, page);\n        } else {\n            onDownloaderFail(request);\n        }\n    }\n\n    private void onDownloadSuccess(Request request, Page page) {\n        if (site.getAcceptStatCode().contains(page.getStatusCode())){\n            pageProcessor.process(page);\n            extractAndAddRequests(page, spawnUrl);\n            if (!page.getResultItems().isSkip()) {\n                for (Pipeline pipeline : pipelines) {\n                    pipeline.process(page.getResultItems(), this);\n                }\n            }\n        } else {\n            logger.info(\"page status code error, page {} , code: {}\", request.getUrl(), page.getStatusCode());\n        }\n        sleep(site.getSleepTime());\n    }\n\n    private void onDownloaderFail(Request request) {\n        if (site.getCycleRetryTimes() == 0) {\n            sleep(site.getSleepTime());\n        } else {\n            // for cycle retry\n            doCycleRetry(request);\n        }\n    }\n\n    private void doCycleRetry(Request request) {\n        Object cycleTriedTimesObject = request.getExtra(Request.CYCLE_TRIED_TIMES);\n        if (cycleTriedTimesObject == null) {\n            addRequest(SerializationUtils.clone(request).setPriority(0).putExtra(Request.CYCLE_TRIED_TIMES, 1));\n        } else {\n            int cycleTriedTimes = (Integer) cycleTriedTimesObject;\n            cycleTriedTimes++;\n            if (cycleTriedTimes < site.getCycleRetryTimes()) {\n                addRequest(SerializationUtils.clone(request).setPriority(0).putExtra(Request.CYCLE_TRIED_TIMES, cycleTriedTimes));\n            }\n        }\n        sleep(site.getRetrySleepTime());\n    }\n\n    protected void sleep(int time) {\n        try {\n            Thread.sleep(time);\n        } catch (InterruptedException e) {\n            logger.error(\"Thread interrupted when sleep\",e);\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    protected void extractAndAddRequests(Page page, boolean spawnUrl) {\n        if (spawnUrl && CollectionUtils.isNotEmpty(page.getTargetRequests())) {\n            for (Request request : page.getTargetRequests()) {\n                addRequest(request);\n            }\n        }\n    }\n\n    private void addRequest(Request request) {\n        if (site.getDomain() == null && request != null && request.getUrl() != null) {\n            site.setDomain(UrlUtils.getDomain(request.getUrl()));\n        }\n        scheduler.push(request, this);\n    }\n\n    protected void checkIfRunning() {\n        if (stat.get() == STAT_RUNNING) {\n            throw new IllegalStateException(\"Spider is already running!\");\n        }\n    }\n\n    public void runAsync() {\n        Thread thread = new Thread(this);\n        thread.setDaemon(false);\n        thread.start();\n    }\n\n    /**\n     * Add urls to crawl. <br>\n     *\n     * @param urls urls\n     * @return this\n     */\n    public Spider addUrl(String... urls) {\n        for (String url : urls) {\n            addRequest(new Request(url));\n        }\n        scheduler.signalNewUrl();\n        return this;\n    }\n\n    /**\n     * Download urls synchronizing.\n     *\n     * @param urls urls\n     * @param <T> type of process result\n     * @return list downloaded\n     */\n    public <T> List<T> getAll(Collection<String> urls) {\n        destroyWhenExit = false;\n        spawnUrl = false;\n        if (startRequests!=null){\n            startRequests.clear();\n        }\n        for (Request request : UrlUtils.convertToRequests(urls)) {\n            addRequest(request);\n        }\n        CollectorPipeline collectorPipeline = getCollectorPipeline();\n        pipelines.add(collectorPipeline);\n        run();\n        spawnUrl = true;\n        destroyWhenExit = true;\n        return collectorPipeline.getCollected();\n    }\n\n    protected CollectorPipeline getCollectorPipeline() {\n        return new ResultItemsCollectorPipeline();\n    }\n\n    public <T> T get(String url) {\n        List<String> urls = WMCollections.newArrayList(url);\n        List<T> resultItemses = getAll(urls);\n        if (resultItemses != null && resultItemses.size() > 0) {\n            return resultItemses.get(0);\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Add urls with information to crawl.<br>\n     *\n     * @param requests requests\n     * @return this\n     */\n    public Spider addRequest(Request... requests) {\n        for (Request request : requests) {\n            addRequest(request);\n        }\n        scheduler.signalNewUrl();\n        return this;\n    }\n\n    public void start() {\n        runAsync();\n    }\n\n    public void stop() {\n        if (stat.compareAndSet(STAT_RUNNING, STAT_STOPPED)) {\n            logger.info(\"Spider \" + getUUID() + \" stop success!\");\n        } else {\n            logger.info(\"Spider \" + getUUID() + \" stop fail!\");\n        }\n    }\n\n    /**\n     * Stop when all tasks in the queue are completed and all worker threads are also completed\n     */\n    public void stopWhenComplete(){\n        this.exitWhenComplete = true;\n    }\n\n    /**\n     * start with more than one threads\n     *\n     * @param threadNum threadNum\n     * @return this\n     */\n    public Spider thread(int threadNum) {\n        checkIfRunning();\n        this.threadNum = threadNum;\n        if (threadNum <= 0) {\n            throw new IllegalArgumentException(\"threadNum should be more than one!\");\n        }\n        return this;\n    }\n\n    /**\n     * start with more than one threads\n     *\n     * @param executorService executorService to run the spider\n     * @param threadNum threadNum\n     * @return this\n     */\n    public Spider thread(ExecutorService executorService, int threadNum) {\n        checkIfRunning();\n        this.threadNum = threadNum;\n        if (threadNum <= 0) {\n            throw new IllegalArgumentException(\"threadNum should be more than one!\");\n        }\n        this.executorService = executorService;\n        return this;\n    }\n\n    public boolean isExitWhenComplete() {\n        return exitWhenComplete;\n    }\n\n    /**\n     * Exit when complete. <br>\n     * True: exit when all url of the site is downloaded. <br>\n     * False: not exit until call stop() manually.<br>\n     *\n     * @param exitWhenComplete exitWhenComplete\n     * @return this\n     */\n    public Spider setExitWhenComplete(boolean exitWhenComplete) {\n        this.exitWhenComplete = exitWhenComplete;\n        return this;\n    }\n\n    public boolean isSpawnUrl() {\n        return spawnUrl;\n    }\n\n    /**\n     * Get page count downloaded by spider.\n     *\n     * @return total downloaded page count\n     * @since 0.4.1\n     */\n    public long getPageCount() {\n        return pageCount.get();\n    }\n\n    /**\n     * Get running status by spider.\n     *\n     * @return running status\n     * @see Status\n     * @since 0.4.1\n     */\n    public Status getStatus() {\n        return Status.fromValue(stat.get());\n    }\n\n\n    public enum Status {\n        Init(0), Running(1), Stopped(2);\n\n        private Status(int value) {\n            this.value = value;\n        }\n\n        private int value;\n\n        int getValue() {\n            return value;\n        }\n\n        public static Status fromValue(int value) {\n            for (Status status : Status.values()) {\n                if (status.getValue() == value) {\n                    return status;\n                }\n            }\n            //default value\n            return Init;\n        }\n    }\n\n    /**\n     * Get thread count which is running\n     *\n     * @return thread count which is running\n     * @since 0.4.1\n     */\n    public int getThreadAlive() {\n        if (threadPool == null) {\n            return 0;\n        }\n        return threadPool.getThreadAlive();\n    }\n\n    /**\n     * Whether add urls extracted to download.<br>\n     * Add urls to download when it is true, and just download seed urls when it is false. <br>\n     * DO NOT set it unless you know what it means!\n     *\n     * @param spawnUrl spawnUrl\n     * @return this\n     * @since 0.4.0\n     */\n    public Spider setSpawnUrl(boolean spawnUrl) {\n        this.spawnUrl = spawnUrl;\n        return this;\n    }\n\n    @Override\n    public String getUUID() {\n        if (uuid != null) {\n            return uuid;\n        }\n        if (site != null) {\n            return site.getDomain();\n        }\n        uuid = UUID.randomUUID().toString();\n        return uuid;\n    }\n\n    public Spider setExecutorService(ExecutorService executorService) {\n        checkIfRunning();\n        this.executorService = executorService;\n        return this;\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public List<SpiderListener> getSpiderListeners() {\n        return spiderListeners;\n    }\n\n    public Spider setSpiderListeners(List<SpiderListener> spiderListeners) {\n        this.spiderListeners = spiderListeners;\n        return this;\n    }\n\n    public Date getStartTime() {\n        return startTime;\n    }\n\n    public Scheduler getScheduler() {\n        return scheduler.getScheduler();\n    }\n\n    /**\n     * Set wait time when no url is polled.<br><br>\n     *\n     * @param emptySleepTime In MILLISECONDS.\n     * @return this\n     */\n    public Spider setEmptySleepTime(long emptySleepTime) {\n        if(emptySleepTime<=0){\n            throw new IllegalArgumentException(\"emptySleepTime should be more than zero!\");\n        }\n        this.emptySleepTime = emptySleepTime;\n        return this;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/SpiderListener.java",
    "content": "package us.codecraft.webmagic;\n\n/**\n * Listener of Spider on page processing. Used for monitor and such on.\n *\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic interface SpiderListener {\n\n    void onSuccess(Request request);\n\n    /**\n     * @deprecated Use {@link #onError(Request, Exception)} instead.\n     */\n    @Deprecated\n    default void onError(Request request) {\n    }\n\n    default void onError(Request request, Exception e) {\n        this.onError(request);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/SpiderScheduler.java",
    "content": "package us.codecraft.webmagic;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Condition;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport us.codecraft.webmagic.scheduler.Scheduler;\nimport us.codecraft.webmagic.thread.CountableThreadPool;\n\npublic class SpiderScheduler {\n    private Scheduler scheduler;\n    private final ReentrantLock newUrlLock = new ReentrantLock();\n    private final Condition newUrlCondition = newUrlLock.newCondition();\n\n    public SpiderScheduler(Scheduler scheduler) {\n        this.scheduler = scheduler;\n    }\n\n    public Scheduler getScheduler() {\n        return scheduler;\n    }\n\n    public void setScheduler(Scheduler scheduler) {\n        this.scheduler = scheduler;\n    }\n\n    public Request poll(Spider spider) {\n        return scheduler.poll(spider);\n    }\n\n    public void push(Request request, Spider spider) {\n        scheduler.push(request, spider);\n    }\n\n    public boolean waitNewUrl(CountableThreadPool threadPool, long emptySleepTime) {\n        newUrlLock.lock();\n        try {\n            if (threadPool.getThreadAlive() == 0) {\n                return false;\n            }\n            newUrlCondition.await(emptySleepTime, TimeUnit.MILLISECONDS);\n            return false;\n        } catch (InterruptedException e) {\n            return true;\n        } finally {\n            newUrlLock.unlock();\n        }\n    }\n\n    public void signalNewUrl() {\n        try {\n            newUrlLock.lock();\n            newUrlCondition.signalAll();\n        } finally {\n            newUrlLock.unlock();\n        }\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/Task.java",
    "content": "package us.codecraft.webmagic;\n\n/**\n * Interface for identifying different tasks.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n * @see us.codecraft.webmagic.scheduler.Scheduler\n * @see us.codecraft.webmagic.pipeline.Pipeline\n */\npublic interface Task {\n\n    /**\n     * unique id for a task.\n     *\n     * @return uuid\n     */\n    public String getUUID();\n\n    /**\n     * site of a task\n     *\n     * @return site\n     */\n    public Site getSite();\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/downloader/AbstractDownloader.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.selector.Html;\n\n/**\n * Base class of downloader with some common methods.\n *\n * @author code4crafter@gmail.com\n * @since 0.5.0\n */\npublic abstract class AbstractDownloader implements Downloader {\n\n    /**\n     * A simple method to download a url.\n     *\n     * @param url url\n     * @return html\n     */\n    public Html download(String url) {\n        return download(url, null);\n    }\n\n    /**\n     * A simple method to download a url.\n     *\n     * @param url     url\n     * @param charset charset\n     * @return html\n     */\n    public Html download(String url, String charset) {\n        Page page = download(new Request(url), Site.me().setCharset(charset).toTask());\n        return (Html) page.getHtml();\n    }\n\n    /**\n     * @param request the {@link Request}.\n     * @deprecated Use {@link #onSuccess(Page, Task)} instead.\n     */\n    @Deprecated\n    protected void onSuccess(Request request) {\n    }\n\n    /**\n     * @param request the {@link Request}.\n     * @param task the {@link Task}.\n     * @since 0.7.6\n     * @deprecated Use {@link #onSuccess(Page, Task)} instead.\n     */\n    @Deprecated\n    protected void onSuccess(Request request, Task task) {\n        this.onSuccess(request);\n    }\n\n    /**\n     * @param page the {@link Page}.\n     * @param task the {@link Task}.\n     * @since 0.10.0\n     */\n    protected void onSuccess(Page page, Task task) {\n        this.onSuccess(page.getRequest(), task);\n    }\n\n    /**\n     * @param request the {@link Request}.\n     * @deprecated Use {@link #onError(Page, Task, Throwable)} instead.\n     */\n    @Deprecated\n    protected void onError(Request request) {\n    }\n\n    /**\n     * @param request the {@link Request}.\n     * @param task the {@link Task}.\n     * @param e the exception.\n     * @since 0.7.6\n     * @deprecated Use {@link #onError(Page, Task, Throwable)} instead.\n     */\n    @Deprecated\n    protected void onError(Request request, Task task, Throwable e) {\n        this.onError(request);\n    }\n\n    /**\n     * @param page the {@link Page}.\n     * @param task the {@link Task}.\n     * @param e the exception.\n     * @since 0.10.0\n     */\n    protected void onError(Page page, Task task, Throwable e) {\n        this.onError(page.getRequest(), task, e);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/downloader/CustomRedirectStrategy.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport java.net.URI;\n\nimport org.apache.http.HttpRequest;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.ProtocolException;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestWrapper;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.impl.client.LaxRedirectStrategy;\nimport org.apache.http.protocol.HttpContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n *支持post 302跳转策略实现类\n *HttpClient默认跳转：httpClientBuilder.setRedirectStrategy(new LaxRedirectStrategy());\n *上述代码在post/redirect/post这种情况下不会传递原有请求的数据信息。所以参考了下SeimiCrawler这个项目的重定向策略。\n *原代码地址：https://github.com/zhegexiaohuozi/SeimiCrawler/blob/master/project/src/main/java/cn/wanghaomiao/seimi/http/hc/SeimiRedirectStrategy.java\n */\npublic class CustomRedirectStrategy extends LaxRedirectStrategy {\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    @Override\n    public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {\n        URI uri = getLocationURI(request, response, context);\n        String method = request.getRequestLine().getMethod();\n        if (\"post\".equalsIgnoreCase(method)) {\n            try {\n                HttpRequestWrapper httpRequestWrapper = (HttpRequestWrapper) request;\n                httpRequestWrapper.setURI(uri);\n                httpRequestWrapper.removeHeaders(\"Content-Length\");\n                return httpRequestWrapper;\n            } catch (Exception e) {\n                logger.error(\"强转为HttpRequestWrapper出错\");\n            }\n            return new HttpPost(uri);\n        } else {\n            return new HttpGet(uri);\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/downloader/Downloader.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\n\n/**\n * Downloader is the part that downloads web pages and store in Page object. <br>\n * Downloader has {@link #setThread(int)} method because downloader is always the bottleneck of a crawler,\n * there are always some mechanisms such as pooling in downloader, and pool size is related to thread numbers.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic interface Downloader {\n\n    /**\n     * Downloads web pages and store in Page object.\n     *\n     * @param request request\n     * @param task task\n     * @return page\n     */\n    public Page download(Request request, Task task);\n\n    /**\n     * Tell the downloader how many threads the spider used.\n     * @param threadNum number of threads\n     */\n    public void setThread(int threadNum);\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/downloader/HttpClientDownloader.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.util.EntityUtils;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.proxy.Proxy;\nimport us.codecraft.webmagic.proxy.ProxyProvider;\nimport us.codecraft.webmagic.selector.PlainText;\nimport us.codecraft.webmagic.utils.CharsetUtils;\nimport us.codecraft.webmagic.utils.HttpClientUtils;\n\n/**\n * The http downloader based on HttpClient.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class HttpClientDownloader extends AbstractDownloader {\n\n    private final Map<String, CloseableHttpClient> httpClients = new HashMap<String, CloseableHttpClient>();\n\n    private HttpClientGenerator httpClientGenerator = new HttpClientGenerator();\n\n    private HttpUriRequestConverter httpUriRequestConverter = new HttpUriRequestConverter();\n\n    private ProxyProvider proxyProvider;\n\n    private boolean responseHeader = true;\n\n    public void setHttpUriRequestConverter(HttpUriRequestConverter httpUriRequestConverter) {\n        this.httpUriRequestConverter = httpUriRequestConverter;\n    }\n\n    public void setProxyProvider(ProxyProvider proxyProvider) {\n        this.proxyProvider = proxyProvider;\n    }\n\n    private CloseableHttpClient getHttpClient(Site site) {\n        if (site == null) {\n            return httpClientGenerator.getClient(null);\n        }\n        String domain = site.getDomain();\n        CloseableHttpClient httpClient = httpClients.get(domain);\n        if (httpClient == null) {\n            synchronized (this) {\n                httpClient = httpClients.get(domain);\n                if (httpClient == null) {\n                    httpClient = httpClientGenerator.getClient(site);\n                    httpClients.put(domain, httpClient);\n                }\n            }\n        }\n        return httpClient;\n    }\n\n    @Override\n    public Page download(Request request, Task task) {\n        if (task == null || task.getSite() == null) {\n            throw new NullPointerException(\"task or site can not be null\");\n        }\n        CloseableHttpResponse httpResponse = null;\n        CloseableHttpClient httpClient = getHttpClient(task.getSite());\n        Proxy proxy = proxyProvider != null ? proxyProvider.getProxy(request, task) : null;\n        HttpClientRequestContext requestContext = httpUriRequestConverter.convert(request, task.getSite(), proxy);\n        Page page = null;\n        try {\n            httpResponse = httpClient.execute(requestContext.getHttpUriRequest(), requestContext.getHttpClientContext());\n            page = handleResponse(request, request.getCharset() != null ? request.getCharset() : task.getSite().getCharset(), httpResponse, task);\n            onSuccess(page, task);\n            return page;\n        } catch (IOException e) {\n            page = Page.ofFailure(request);\n            onError(page, task, e);\n            return page;\n        } finally {\n            if (httpResponse != null) {\n                //ensure the connection is released back to pool\n                EntityUtils.consumeQuietly(httpResponse.getEntity());\n            }\n            if (proxyProvider != null && proxy != null) {\n                proxyProvider.returnProxy(proxy, page, task);\n            }\n        }\n    }\n\n    @Override\n    public void setThread(int thread) {\n        httpClientGenerator.setPoolSize(thread);\n    }\n\n    protected Page handleResponse(Request request, String charset, HttpResponse httpResponse, Task task) throws IOException {\n        HttpEntity entity = httpResponse.getEntity();\n        byte[] bytes = entity != null ? IOUtils.toByteArray(entity.getContent()) : new byte[0];\n        String contentType = entity != null && entity.getContentType() != null ? entity.getContentType().getValue() : null;\n        Page page = Page.ofSuccess(request);\n        page.setBytes(bytes);\n        if (!request.isBinaryContent()) {\n            if (charset == null) {\n                charset = getHtmlCharset(contentType, bytes, task);\n            }\n            page.setCharset(charset);\n            page.setRawText(new String(bytes, charset));\n        }\n        page.setUrl(new PlainText(request.getUrl()));\n        page.setRequest(request);\n        page.setStatusCode(httpResponse.getStatusLine().getStatusCode());\n        if (responseHeader) {\n            page.setHeaders(HttpClientUtils.convertHeaders(httpResponse.getAllHeaders()));\n        }\n        return page;\n    }\n\n    private String getHtmlCharset(String contentType, byte[] contentBytes, Task task) throws IOException {\n        String charset = CharsetUtils.detectCharset(contentType, contentBytes);\n        if (charset == null) {\n            charset = Optional.ofNullable(task.getSite().getDefaultCharset()).orElseGet(Charset.defaultCharset()::name);\n        }\n        return charset;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/downloader/HttpClientGenerator.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport org.apache.commons.lang3.JavaVersion;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.apache.http.HttpException;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.HttpRequestInterceptor;\nimport org.apache.http.client.CookieStore;\nimport org.apache.http.config.Registry;\nimport org.apache.http.config.RegistryBuilder;\nimport org.apache.http.config.SocketConfig;\nimport org.apache.http.conn.socket.ConnectionSocketFactory;\nimport org.apache.http.conn.socket.PlainConnectionSocketFactory;\nimport org.apache.http.conn.ssl.SSLConnectionSocketFactory;\nimport org.apache.http.impl.client.*;\nimport org.apache.http.impl.conn.PoolingHttpClientConnectionManager;\nimport org.apache.http.impl.cookie.BasicClientCookie;\nimport org.apache.http.protocol.HttpContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.Site;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\nimport java.io.IOException;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.Map;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.4.0\n */\npublic class HttpClientGenerator {\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    private PoolingHttpClientConnectionManager connectionManager;\n\n    public HttpClientGenerator() {\n        Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()\n                .register(\"http\", PlainConnectionSocketFactory.INSTANCE)\n                .register(\"https\", buildSSLConnectionSocketFactory())\n                .build();\n        connectionManager = new PoolingHttpClientConnectionManager(reg);\n        connectionManager.setDefaultMaxPerRoute(100);\n    }\n\n    private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() {\n        try {\n            SSLContext sslContext = createIgnoreVerifySSL();\n            String[] supportedProtocols;\n            if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_11)) {\n                supportedProtocols = new String[]{\"SSLv3\", \"TLSv1\", \"TLSv1.1\", \"TLSv1.2\", \"TLSv1.3\"};\n            } else {\n                supportedProtocols = new String[]{\"SSLv3\", \"TLSv1\", \"TLSv1.1\", \"TLSv1.2\"};\n            }\n            logger.debug(\"supportedProtocols: {}\", String.join(\", \", supportedProtocols));\n            return new SSLConnectionSocketFactory(sslContext, supportedProtocols,\n                    null,\n                    //不进行主机校验\n                    (host, sslSession) -> true); // 优先绕过安全证书\n        } catch (KeyManagementException | NoSuchAlgorithmException e) {\n            logger.error(\"ssl connection fail\", e);\n        }\n        return SSLConnectionSocketFactory.getSocketFactory();\n    }\n\n    private SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {\n        // 实现一个X509TrustManager接口，用于绕过验证，不用修改里面的方法\n        X509TrustManager trustManager = new X509TrustManager() {\n\n            @Override\n            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n            }\n\n            @Override\n            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n            }\n\n            @Override\n            public X509Certificate[] getAcceptedIssuers() {\n                return null;\n            }\n\n        };\n\n        SSLContext sc = SSLContext.getInstance(\"TLS\");\n        sc.init(null, new TrustManager[]{trustManager}, null);\n        return sc;\n    }\n\n    public HttpClientGenerator setPoolSize(int poolSize) {\n        connectionManager.setMaxTotal(poolSize);\n        return this;\n    }\n\n    public CloseableHttpClient getClient(Site site) {\n        return generateClient(site);\n    }\n\n    private CloseableHttpClient generateClient(Site site) {\n        HttpClientBuilder httpClientBuilder = HttpClients.custom();\n\n        httpClientBuilder.setConnectionManager(connectionManager);\n        if (site.getUserAgent() != null) {\n            httpClientBuilder.setUserAgent(site.getUserAgent());\n        } else {\n            httpClientBuilder.setUserAgent(\"\");\n        }\n        if (site.isUseGzip()) {\n            httpClientBuilder.addInterceptorFirst(new HttpRequestInterceptor() {\n\n                public void process(\n                        final HttpRequest request,\n                        final HttpContext context) throws HttpException, IOException {\n                    if (!request.containsHeader(\"Accept-Encoding\")) {\n                        request.addHeader(\"Accept-Encoding\", \"gzip\");\n                    }\n                }\n            });\n        }\n        //解决post/redirect/post 302跳转问题\n        httpClientBuilder.setRedirectStrategy(new CustomRedirectStrategy());\n\n        SocketConfig.Builder socketConfigBuilder = SocketConfig.custom();\n        socketConfigBuilder.setSoKeepAlive(true).setTcpNoDelay(true);\n        socketConfigBuilder.setSoTimeout(site.getTimeOut());\n        SocketConfig socketConfig = socketConfigBuilder.build();\n        httpClientBuilder.setDefaultSocketConfig(socketConfig);\n        connectionManager.setDefaultSocketConfig(socketConfig);\n        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(site.getRetryTimes(), true));\n        generateCookie(httpClientBuilder, site);\n        return httpClientBuilder.build();\n    }\n\n    private void generateCookie(HttpClientBuilder httpClientBuilder, Site site) {\n        if (site.isDisableCookieManagement()) {\n            httpClientBuilder.disableCookieManagement();\n            return;\n        }\n        CookieStore cookieStore = new BasicCookieStore();\n        for (Map.Entry<String, String> cookieEntry : site.getCookies().entrySet()) {\n            BasicClientCookie cookie = new BasicClientCookie(cookieEntry.getKey(), cookieEntry.getValue());\n            cookie.setDomain(site.getDomain());\n            cookieStore.addCookie(cookie);\n        }\n        for (Map.Entry<String, Map<String, String>> domainEntry : site.getAllCookies().entrySet()) {\n            for (Map.Entry<String, String> cookieEntry : domainEntry.getValue().entrySet()) {\n                BasicClientCookie cookie = new BasicClientCookie(cookieEntry.getKey(), cookieEntry.getValue());\n                cookie.setDomain(domainEntry.getKey());\n                cookieStore.addCookie(cookie);\n            }\n        }\n        httpClientBuilder.setDefaultCookieStore(cookieStore);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/downloader/HttpClientRequestContext.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.client.protocol.HttpClientContext;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/4/8\n *         Time: 19:43\n * @since 0.7.0\n */\npublic class HttpClientRequestContext {\n\n    private HttpUriRequest httpUriRequest;\n\n    private HttpClientContext httpClientContext;\n\n    public HttpUriRequest getHttpUriRequest() {\n        return httpUriRequest;\n    }\n\n    public void setHttpUriRequest(HttpUriRequest httpUriRequest) {\n        this.httpUriRequest = httpUriRequest;\n    }\n\n    public HttpClientContext getHttpClientContext() {\n        return httpClientContext;\n    }\n\n    public void setHttpClientContext(HttpClientContext httpClientContext) {\n        this.httpClientContext = httpClientContext;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/downloader/HttpUriRequestConverter.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport org.apache.http.HttpHost;\nimport org.apache.http.auth.AuthState;\nimport org.apache.http.auth.ChallengeState;\nimport org.apache.http.auth.UsernamePasswordCredentials;\nimport org.apache.http.client.CookieStore;\nimport org.apache.http.client.config.CookieSpecs;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.client.methods.RequestBuilder;\nimport org.apache.http.client.protocol.HttpClientContext;\nimport org.apache.http.entity.ByteArrayEntity;\nimport org.apache.http.impl.auth.BasicScheme;\nimport org.apache.http.impl.client.BasicCookieStore;\nimport org.apache.http.impl.cookie.BasicClientCookie;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.proxy.Proxy;\nimport us.codecraft.webmagic.utils.HttpConstant;\nimport us.codecraft.webmagic.utils.UrlUtils;\n\nimport java.util.Map;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/3/18\n *         Time: 11:28\n *\n * @since 0.7.0\n */\npublic class HttpUriRequestConverter {\n\n    public HttpClientRequestContext convert(Request request, Site site, Proxy proxy) {\n        HttpClientRequestContext httpClientRequestContext = new HttpClientRequestContext();\n        httpClientRequestContext.setHttpUriRequest(convertHttpUriRequest(request, site, proxy));\n        httpClientRequestContext.setHttpClientContext(convertHttpClientContext(request, site, proxy));\n        return httpClientRequestContext;\n    }\n\n    private HttpClientContext convertHttpClientContext(Request request, Site site, Proxy proxy) {\n        HttpClientContext httpContext = new HttpClientContext();\n        if (proxy != null && proxy.getUsername() != null) {\n            AuthState authState = new AuthState();\n            BasicScheme proxyAuthScheme = new BasicScheme(ChallengeState.PROXY);\n            UsernamePasswordCredentials proxyCredentials = new UsernamePasswordCredentials(proxy.getUsername(), proxy.getPassword());\n            authState.update(proxyAuthScheme, proxyCredentials);\n            httpContext.setAttribute(HttpClientContext.PROXY_AUTH_STATE, authState);\n        }\n        if (request.getCookies() != null && !request.getCookies().isEmpty()) {\n            CookieStore cookieStore = new BasicCookieStore();\n            for (Map.Entry<String, String> cookieEntry : request.getCookies().entrySet()) {\n                BasicClientCookie cookie1 = new BasicClientCookie(cookieEntry.getKey(), cookieEntry.getValue());\n                cookie1.setDomain(UrlUtils.removePort(UrlUtils.getDomain(request.getUrl())));\n                cookieStore.addCookie(cookie1);\n            }\n            httpContext.setCookieStore(cookieStore);\n        }\n        return httpContext;\n    }\n\n    private HttpUriRequest convertHttpUriRequest(Request request, Site site, Proxy proxy) {\n        RequestBuilder requestBuilder = selectRequestMethod(request).setUri(UrlUtils.fixIllegalCharacterInUrl(request.getUrl()));\n        if (site.getHeaders() != null) {\n            for (Map.Entry<String, String> headerEntry : site.getHeaders().entrySet()) {\n                requestBuilder.addHeader(headerEntry.getKey(), headerEntry.getValue());\n            }\n        }\n\n        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();\n        if (site != null) {\n            requestConfigBuilder.setConnectionRequestTimeout(site.getTimeOut())\n                    .setSocketTimeout(site.getTimeOut())\n                    .setConnectTimeout(site.getTimeOut())\n                    .setCookieSpec(CookieSpecs.STANDARD);\n        }\n\n        if (proxy != null) {\n            requestConfigBuilder.setProxy(new HttpHost(proxy.getHost(), proxy.getPort(), proxy.getScheme()));\n        }\n        requestBuilder.setConfig(requestConfigBuilder.build());\n        HttpUriRequest httpUriRequest = requestBuilder.build();\n        if (request.getHeaders() != null && !request.getHeaders().isEmpty()) {\n            for (Map.Entry<String, String> header : request.getHeaders().entrySet()) {\n                httpUriRequest.addHeader(header.getKey(), header.getValue());\n            }\n        }\n        return httpUriRequest;\n    }\n\n    private RequestBuilder selectRequestMethod(Request request) {\n        String method = request.getMethod();\n        if (method == null || method.equalsIgnoreCase(HttpConstant.Method.GET)) {\n            //default get\n            return RequestBuilder.get();\n        } else if (method.equalsIgnoreCase(HttpConstant.Method.POST)) {\n            return addFormParams(RequestBuilder.post(),request);\n        } else if (method.equalsIgnoreCase(HttpConstant.Method.HEAD)) {\n            return RequestBuilder.head();\n        } else if (method.equalsIgnoreCase(HttpConstant.Method.PUT)) {\n            return addFormParams(RequestBuilder.put(), request);\n        } else if (method.equalsIgnoreCase(HttpConstant.Method.DELETE)) {\n            return RequestBuilder.delete();\n        } else if (method.equalsIgnoreCase(HttpConstant.Method.TRACE)) {\n            return RequestBuilder.trace();\n        }\n        throw new IllegalArgumentException(\"Illegal HTTP Method \" + method);\n    }\n\n    private RequestBuilder addFormParams(RequestBuilder requestBuilder, Request request) {\n        if (request.getRequestBody() != null) {\n            ByteArrayEntity entity = new ByteArrayEntity(request.getRequestBody().getBody());\n            entity.setContentType(request.getRequestBody().getContentType());\n            requestBuilder.setEntity(entity);\n        }\n        return requestBuilder;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/downloader/package.html",
    "content": "<html>\n\t<body>\nDownloader is the part that downloads web pages and store in Page object.\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/model/HttpRequestBody.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport org.apache.http.NameValuePair;\nimport org.apache.http.client.utils.URLEncodedUtils;\nimport org.apache.http.message.BasicNameValuePair;\n\nimport java.io.Serializable;\nimport java.io.UnsupportedEncodingException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/4/8\n */\npublic class HttpRequestBody implements Serializable {\n\n    private static final long serialVersionUID = 5659170945717023595L;\n\n    public static abstract class ContentType {\n\n        public static final String JSON = \"application/json\";\n\n        public static final String XML = \"text/xml\";\n\n        public static final String FORM = \"application/x-www-form-urlencoded\";\n\n        public static final String MULTIPART = \"multipart/form-data\";\n    }\n\n    private byte[] body;\n\n    private String contentType;\n\n    private String encoding;\n\n    public HttpRequestBody() {\n    }\n\n    public HttpRequestBody(byte[] body, String contentType, String encoding) {\n        this.body = body;\n        this.contentType = contentType;\n        this.encoding = encoding;\n    }\n\n    public String getContentType() {\n        return contentType;\n    }\n\n    public String getEncoding() {\n        return encoding;\n    }\n\n    public void setBody(byte[] body) {\n        this.body = body;\n    }\n\n    public void setContentType(String contentType) {\n        this.contentType = contentType;\n    }\n\n    public void setEncoding(String encoding) {\n        this.encoding = encoding;\n    }\n\n    public static HttpRequestBody json(String json, String encoding) {\n        try {\n            return new HttpRequestBody(json.getBytes(encoding), ContentType.JSON, encoding);\n        } catch (UnsupportedEncodingException e) {\n            throw new IllegalArgumentException(\"illegal encoding \" + encoding, e);\n        }\n    }\n\n    public static HttpRequestBody xml(String xml, String encoding) {\n        try {\n            return new HttpRequestBody(xml.getBytes(encoding), ContentType.XML, encoding);\n        } catch (UnsupportedEncodingException e) {\n            throw new IllegalArgumentException(\"illegal encoding \" + encoding, e);\n        }\n    }\n\n    public static HttpRequestBody custom(byte[] body, String contentType, String encoding) {\n        return new HttpRequestBody(body, contentType, encoding);\n    }\n\n    public static HttpRequestBody form(Map<String,Object> params, String encoding){\n        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(params.size());\n        for (Map.Entry<String, Object> entry : params.entrySet()) {\n            nameValuePairs.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));\n        }\n        try {\n            return new HttpRequestBody(URLEncodedUtils.format(nameValuePairs, encoding).getBytes(encoding), ContentType.FORM, encoding);\n        } catch (UnsupportedEncodingException e) {\n            throw new IllegalArgumentException(\"illegal encoding \" + encoding, e);\n        }\n    }\n\n    public byte[] getBody() {\n        return body;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/package.html",
    "content": "<html>\n\t<body>\n    <div class=\"en\">\n        Main class \"Spider\" and models.\n    </div>\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/pipeline/CollectorPipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport java.util.List;\n\n/**\n * Pipeline that can collect and store results. <br>\n * Used for {@link us.codecraft.webmagic.Spider#getAll(java.util.Collection)}\n *\n * @author code4crafter@gmail.com\n * @since 0.4.0\n */\npublic interface CollectorPipeline<T> extends Pipeline {\n\n    /**\n     * Get all results collected.\n     *\n     * @return collected results\n     */\n    public List<T> getCollected();\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/pipeline/ConsolePipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\n\nimport java.util.Map;\n\n/**\n * Write results in console.<br>\n * Usually used in test.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class ConsolePipeline implements Pipeline {\n\n    @Override\n    public void process(ResultItems resultItems, Task task) {\n        System.out.println(\"get page: \" + resultItems.getRequest().getUrl());\n        for (Map.Entry<String, Object> entry : resultItems.getAll().entrySet()) {\n            System.out.println(entry.getKey() + \":\\t\" + entry.getValue());\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/pipeline/FilePipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.utils.FilePersistentBase;\n\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.io.PrintWriter;\nimport java.util.Map;\n\n/**\n * Store results in files.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class FilePipeline extends FilePersistentBase implements Pipeline {\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    /**\n     * create a FilePipeline with default path\"/data/webmagic/\"\n     */\n    public FilePipeline() {\n        setPath(\"/data/webmagic/\");\n    }\n\n    public FilePipeline(String path) {\n        setPath(path);\n    }\n\n    @Override\n    public void process(ResultItems resultItems, Task task) {\n        String path = this.path + PATH_SEPERATOR + task.getUUID() + PATH_SEPERATOR;\n        try {\n            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(getFile(path + DigestUtils.md5Hex(resultItems.getRequest().getUrl()) + \".html\")),\"UTF-8\"));\n            printWriter.println(\"url:\\t\" + resultItems.getRequest().getUrl());\n            for (Map.Entry<String, Object> entry : resultItems.getAll().entrySet()) {\n                if (entry.getValue() instanceof Iterable) {\n                    Iterable value = (Iterable) entry.getValue();\n                    printWriter.println(entry.getKey() + \":\");\n                    for (Object o : value) {\n                        printWriter.println(o);\n                    }\n                } else {\n                    printWriter.println(entry.getKey() + \":\\t\" + entry.getValue());\n                }\n            }\n            printWriter.close();\n        } catch (IOException e) {\n            logger.warn(\"write file error\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/pipeline/Pipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\n\n/**\n * Pipeline is the persistent and offline process part of crawler.<br>\n * The interface Pipeline can be implemented to customize ways of persistent.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n * @see ConsolePipeline\n * @see FilePipeline\n */\npublic interface Pipeline {\n\n    /**\n     * Process extracted results.\n     *\n     * @param resultItems resultItems\n     * @param task task\n     */\n    public void process(ResultItems resultItems, Task task);\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/pipeline/ResultItemsCollectorPipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.4.0\n */\npublic class ResultItemsCollectorPipeline implements CollectorPipeline<ResultItems> {\n\n    private List<ResultItems> collector = new ArrayList<ResultItems>();\n\n    @Override\n    public synchronized void process(ResultItems resultItems, Task task) {\n        collector.add(resultItems);\n    }\n\n    @Override\n    public List<ResultItems> getCollected() {\n        return collector;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/pipeline/package.html",
    "content": "<html>\n\t<body>\nPipeline is the persistent and offline process part of crawler.\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/processor/PageProcessor.java",
    "content": "package us.codecraft.webmagic.processor;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\n\n/**\n * Interface to be implemented to customize a crawler.\n *\n * <p>\n * In PageProcessor, you can customize:\n * </p>\n * <ul>\n * <li>start URLs and other settings in {@link Site}</li>\n * <li>how the URLs to fetch are detected</li>\n * <li>how the data are extracted and stored</li>\n * </ul>\n *\n * @author code4crafter@gmail.com <br>\n * @see Site\n * @see Page\n * @since 0.1.0\n */\npublic interface PageProcessor {\n\n    /**\n     * Processes the page, extract URLs to fetch, extract the data and store.\n     *\n     * @param page page\n     */\n    void process(Page page);\n\n    /**\n     * Returns the site settings.\n     *\n     * @return site\n     * @see Site\n     */\n    default Site getSite() {\n        return Site.me();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/processor/SimplePageProcessor.java",
    "content": "package us.codecraft.webmagic.processor;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\n\nimport java.util.List;\n\n/**\n * A simple PageProcessor.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class SimplePageProcessor implements PageProcessor {\n\n    private String urlPattern;\n\n    private Site site;\n\n    public SimplePageProcessor(String urlPattern) {\n        this.site = Site.me();\n        //compile \"*\" expression to regex\n        this.urlPattern = \"(\" + urlPattern.replace(\".\", \"\\\\.\").replace(\"*\", \"[^\\\"'#]*\") + \")\";\n\n    }\n\n    @Override\n    public void process(Page page) {\n        List<String> requests = page.getHtml().links().regex(urlPattern).all();\n        //add urls to fetch\n        page.addTargetRequests(requests);\n        //extract by XPath\n        page.putField(\"title\", page.getHtml().xpath(\"//title\"));\n        page.putField(\"html\", page.getHtml().toString());\n        //extract by Readability\n        page.putField(\"content\", page.getHtml().smartContent());\n    }\n\n    @Override\n    public Site getSite() {\n        //settings\n        return site;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/processor/example/BaiduBaikePageProcessor.java",
    "content": "package us.codecraft.webmagic.processor.example;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.4.0\n */\npublic class BaiduBaikePageProcessor implements PageProcessor {\n\n    private Site site = Site.me()//.setHttpProxy(new HttpHost(\"127.0.0.1\",8888))\n            .setRetryTimes(3).setSleepTime(1000).setUseGzip(true);\n\n    @Override\n    public void process(Page page) {\n        page.putField(\"name\", page.getHtml().css(\"dl.lemmaWgt-lemmaTitle h1\",\"text\").toString());\n        page.putField(\"description\", page.getHtml().xpath(\"//div[@class='lemma-summary']/allText()\"));\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        //single download\n        Spider spider = Spider.create(new BaiduBaikePageProcessor()).thread(2);\n        String urlTemplate = \"http://baike.baidu.com/search/word?word=%s&pic=1&sug=1&enc=utf8\";\n        ResultItems resultItems = spider.<ResultItems>get(String.format(urlTemplate, \"水力发电\"));\n        System.out.println(resultItems);\n\n        //multidownload\n        List<String> list = new ArrayList<String>();\n        list.add(String.format(urlTemplate,\"风力发电\"));\n        list.add(String.format(urlTemplate,\"太阳能\"));\n        list.add(String.format(urlTemplate,\"地热发电\"));\n        list.add(String.format(urlTemplate,\"地热发电\"));\n        List<ResultItems> resultItemses = spider.<ResultItems>getAll(list);\n        for (ResultItems resultItemse : resultItemses) {\n            System.out.println(resultItemse.getAll());\n        }\n        spider.close();\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/processor/example/GithubRepoPageProcessor.java",
    "content": "package us.codecraft.webmagic.processor.example;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.3.2\n */\npublic class GithubRepoPageProcessor implements PageProcessor {\n\n    private Site site = Site.me().setRetryTimes(3).setSleepTime(1000).setTimeOut(10000);\n\n    @Override\n    public void process(Page page) {\n        page.addTargetRequests(page.getHtml().links().regex(\"(https://github\\\\.com/[\\\\w\\\\-]+/[\\\\w\\\\-]+)\").all());\n        page.addTargetRequests(page.getHtml().links().regex(\"(https://github\\\\.com/[\\\\w\\\\-])\").all());\n        page.putField(\"author\", page.getUrl().regex(\"https://github\\\\.com/(\\\\w+)/.*\").toString());\n        page.putField(\"name\", page.getHtml().xpath(\"//h1[@class='public']/strong/a/text()\").toString());\n        if (page.getResultItems().get(\"name\")==null){\n            //skip this page\n            page.setSkip(true);\n        }\n        page.putField(\"readme\", page.getHtml().xpath(\"//div[@id='readme']/tidyText()\"));\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new GithubRepoPageProcessor()).addUrl(\"https://github.com/code4craft\").thread(5).run();\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/processor/example/ZhihuPageProcessor.java",
    "content": "package us.codecraft.webmagic.processor.example;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.6.0\n */\npublic class ZhihuPageProcessor implements PageProcessor {\n\n    private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);\n\n    @Override\n    public void process(Page page) {\n        page.addTargetRequests(page.getHtml().links().regex(\"https://www\\\\.zhihu\\\\.com/question/\\\\d+/answer/\\\\d+.*\").all());\n        page.putField(\"title\", page.getHtml().xpath(\"//h1[@class='QuestionHeader-title']/text()\").toString());\n        page.putField(\"question\", page.getHtml().xpath(\"//div[@class='QuestionRichText']//tidyText()\").toString());\n        page.putField(\"answer\", page.getHtml().xpath(\"//div[@class='QuestionAnswer-content']/tidyText()\").toString());\n        if (page.getResultItems().get(\"title\")==null){\n            //skip this page\n            page.setSkip(true);\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new ZhihuPageProcessor()).addUrl(\"https://www.zhihu.com/explore\").run();\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/processor/package.html",
    "content": "<html>\n\t<body>\nPageProcessor custom part of a crawler for specific site.\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/proxy/Proxy.java",
    "content": "package us.codecraft.webmagic.proxy;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\n\nimport org.apache.commons.lang3.StringUtils;\n\npublic class Proxy {\n\n    private String scheme;\n\n    private String host;\n\n    private int port;\n\n    private String username;\n\n    private String password;\n\n    public static Proxy create(final URI uri) {\n        Proxy proxy = new Proxy(uri.getHost(), uri.getPort(), uri.getScheme());\n        String userInfo = uri.getUserInfo();\n        if (userInfo != null) {\n            String[] up = userInfo.split(\":\");\n            if (up.length == 1) {\n                proxy.username = up[0].isEmpty() ? null : up[0];\n            } else {\n                proxy.username = up[0].isEmpty() ? null : up[0];\n                proxy.password = up[1].isEmpty() ? null : up[1];\n            }\n        }\n        return proxy;\n    }\n\n    public Proxy(String host, int port) {\n        this(host, port, null);\n    }\n\n    public Proxy(String host, int port, String scheme) {\n        this.host = host;\n        this.port = port;\n        this.scheme = scheme;\n    }\n\n    public Proxy(String host, int port, String username, String password) {\n        this.host = host;\n        this.port = port;\n        this.username = username;\n        this.password = password;\n    }\n\n    public String getScheme() {\n        return scheme;\n    }\n\n    public void setScheme(String scheme) {\n        this.scheme = scheme;\n    }\n\n\tpublic String getHost() {\n        return host;\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    public String getUsername() {\n        return username;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public URI toURI() {\n        final StringBuilder userInfoBuffer = new StringBuilder();\n        if (username != null) {\n            userInfoBuffer.append(urlencode(username));\n        }\n        if (password != null) {\n            userInfoBuffer.append(\":\").append(urlencode(password));\n        }\n        final String userInfo = StringUtils.defaultIfEmpty(userInfoBuffer.toString(), null);\n        URI uri;\n        try {\n            uri = new URI(scheme, userInfo, host, port, null, null, null);\n        } catch (URISyntaxException e) {\n            throw new IllegalArgumentException(e.getMessage(), e);\n        }\n        return uri;\n    }\n\n    private String urlencode(String s) {\n        String enc = StandardCharsets.UTF_8.name();\n        try {\n            return URLEncoder.encode(s, enc);\n        } catch (UnsupportedEncodingException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        Proxy proxy = (Proxy) o;\n\n        if (port != proxy.port) return false;\n        if (host != null ? !host.equals(proxy.host) : proxy.host != null) return false;\n        if (scheme != null ? !scheme.equals(proxy.scheme) : proxy.scheme != null) return false;\n        if (username != null ? !username.equals(proxy.username) : proxy.username != null) return false;\n        return password != null ? password.equals(proxy.password) : proxy.password == null;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = host != null ? host.hashCode() : 0;\n        result = 31 * result + port;\n        result = 31 * result + (scheme != null ? scheme.hashCode() : 0);\n        result = 31 * result + (username != null ? username.hashCode() : 0);\n        result = 31 * result + (password != null ? password.hashCode() : 0);\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return this.toURI().toString();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/proxy/ProxyProvider.java",
    "content": "package us.codecraft.webmagic.proxy;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\n\n/**\n * Proxy provider. <br>\n *     \n * @since 0.7.0\n */\npublic interface ProxyProvider {\n\n    /**\n     *\n     * Return proxy to Provider when complete a download.\n     * @param proxy the proxy config contains host,port and identify info\n     * @param page the download result\n     * @param task the download task\n     */\n    void returnProxy(Proxy proxy, Page page, Task task);\n\n    /**\n     * Get a proxy for task by some strategy.\n     * @param task the download task\n     * @return proxy \n     * @deprecated Use {@link #getProxy(Request, Task)} instead.\n     */\n    @Deprecated\n    default Proxy getProxy(Task task) {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Returns a proxy for the request.\n     *\n     * @param request the request\n     * @param task the download task\n     * @return proxy\n     * @since 0.9.0\n     */\n    default Proxy getProxy(Request request, Task task) {\n        return this.getProxy(task);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/proxy/SimpleProxyProvider.java",
    "content": "package us.codecraft.webmagic.proxy;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * A simple ProxyProvider. Provide proxy as round-robin without heartbeat and error check. It can be used when all proxies are stable.\n * @author code4crafter@gmail.com\n *         Date: 17/4/16\n *         Time: 10:18\n * @since 0.7.0\n */\npublic class SimpleProxyProvider implements ProxyProvider {\n\n    private final List<Proxy> proxies;\n\n    private final AtomicInteger pointer;\n\n    public SimpleProxyProvider(List<Proxy> proxies) {\n        this(proxies, new AtomicInteger(-1));\n    }\n\n    private SimpleProxyProvider(List<Proxy> proxies, AtomicInteger pointer) {\n        this.proxies = proxies;\n        this.pointer = pointer;\n    }\n\n    public static SimpleProxyProvider from(Proxy... proxies) {\n        List<Proxy> proxiesTemp = new ArrayList<Proxy>(proxies.length);\n        for (Proxy proxy : proxies) {\n            proxiesTemp.add(proxy);\n        }\n        return new SimpleProxyProvider(Collections.unmodifiableList(proxiesTemp));\n    }\n\n    @Override\n    public void returnProxy(Proxy proxy, Page page, Task task) {\n        //Donothing\n    }\n\n    @Override\n    public Proxy getProxy(Request request, Task task) {\n        return proxies.get(incrForLoop());\n    }\n\n    private int incrForLoop() {\n        int p = pointer.incrementAndGet();\n        int size = proxies.size();\n        if (p < size) {\n            return p;\n        }\n        while (!pointer.compareAndSet(p, p % size)) {\n            p = pointer.get();\n        }\n        return p % size;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/DuplicateRemovedScheduler.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.scheduler.component.DuplicateRemover;\nimport us.codecraft.webmagic.scheduler.component.HashSetDuplicateRemover;\nimport us.codecraft.webmagic.utils.HttpConstant;\n\n/**\n * Remove duplicate urls and only push urls which are not duplicate.<br><br>\n *\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic abstract class DuplicateRemovedScheduler implements Scheduler {\n\n    protected Logger logger = LoggerFactory.getLogger(getClass());\n\n    private DuplicateRemover duplicatedRemover = new HashSetDuplicateRemover();\n\n    public DuplicateRemover getDuplicateRemover() {\n        return duplicatedRemover;\n    }\n\n    public DuplicateRemovedScheduler setDuplicateRemover(DuplicateRemover duplicatedRemover) {\n        this.duplicatedRemover = duplicatedRemover;\n        return this;\n    }\n\n    @Override\n    public void push(Request request, Task task) {\n        logger.trace(\"get a candidate url {}\", request.getUrl());\n        if (shouldReserved(request) || noNeedToRemoveDuplicate(request) || !duplicatedRemover.isDuplicate(request, task)) {\n            logger.debug(\"push to queue {}\", request.getUrl());\n            pushWhenNoDuplicate(request, task);\n        }\n    }\n\n    protected boolean shouldReserved(Request request) {\n        return request.getExtra(Request.CYCLE_TRIED_TIMES) != null;\n    }\n\n    protected boolean noNeedToRemoveDuplicate(Request request) {\n        return HttpConstant.Method.POST.equalsIgnoreCase(request.getMethod());\n    }\n\n    protected void pushWhenNoDuplicate(Request request, Task task) {\n\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/MonitorableScheduler.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport us.codecraft.webmagic.Task;\n\n/**\n * The scheduler whose requests can be counted for monitor.\n *\n * @author code4crafter@gmail.com\n * @since 0.5.0\n */\npublic interface MonitorableScheduler extends Scheduler {\n\n    public int getLeftRequestsCount(Task task);\n\n    public int getTotalRequestsCount(Task task);\n\n}"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/PriorityScheduler.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.utils.NumberUtils;\n\nimport java.util.Comparator;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.PriorityBlockingQueue;\n\n/**\n * Priority scheduler. Request with higher priority will poll earlier. <br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.1\n */\npublic class PriorityScheduler extends DuplicateRemovedScheduler implements MonitorableScheduler {\n\n    public static final int INITIAL_CAPACITY = 5;\n\n    private BlockingQueue<Request> noPriorityQueue = new LinkedBlockingQueue<Request>();\n\n    private PriorityBlockingQueue<Request> priorityQueuePlus = new PriorityBlockingQueue<Request>(INITIAL_CAPACITY, new Comparator<Request>() {\n        @Override\n        public int compare(Request o1, Request o2) {\n            return -NumberUtils.compareLong(o1.getPriority(), o2.getPriority());\n        }\n    });\n\n    private PriorityBlockingQueue<Request> priorityQueueMinus = new PriorityBlockingQueue<Request>(INITIAL_CAPACITY, new Comparator<Request>() {\n        @Override\n        public int compare(Request o1, Request o2) {\n            return -NumberUtils.compareLong(o1.getPriority(), o2.getPriority());\n        }\n    });\n\n    @Override\n    public void pushWhenNoDuplicate(Request request, Task task) {\n        if (request.getPriority() == 0) {\n            noPriorityQueue.add(request);\n        } else if (request.getPriority() > 0) {\n            priorityQueuePlus.put(request);\n        } else {\n            priorityQueueMinus.put(request);\n        }\n    }\n\n    @Override\n    public synchronized Request poll(Task task) {\n        Request poll = priorityQueuePlus.poll();\n        if (poll != null) {\n            return poll;\n        }\n        poll = noPriorityQueue.poll();\n        if (poll != null) {\n            return poll;\n        }\n        return priorityQueueMinus.poll();\n    }\n\n    @Override\n    public int getLeftRequestsCount(Task task) {\n        return noPriorityQueue.size();\n    }\n\n    @Override\n    public int getTotalRequestsCount(Task task) {\n        return getDuplicateRemover().getTotalRequestsCount(task);\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/QueueScheduler.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.LinkedBlockingQueue;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\n\n/**\n * Basic Scheduler implementation.<br>\n * Store urls to fetch in LinkedBlockingQueue and remove duplicate urls by HashMap.\n *\n * Note: if you use this {@link QueueScheduler}\n * with {@link Site#getCycleRetryTimes()} enabled, you may encountered dead-lock\n * when the queue is full.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class QueueScheduler extends DuplicateRemovedScheduler implements MonitorableScheduler {\n\n    private final BlockingQueue<Request> queue;\n\n    public QueueScheduler() {\n        this.queue = new LinkedBlockingQueue<>();\n    }\n\n    /**\n     * Creates a {@code QueueScheduler} with the given (fixed) capacity.\n     *\n     * @param capacity the capacity of this queue,\n     * see {@link LinkedBlockingQueue#LinkedBlockingQueue(int)}\n     * @since 0.8.0\n     */\n    public QueueScheduler(int capacity) {\n        this.queue = new LinkedBlockingQueue<>(capacity);\n    }\n\n    @Override\n    public void pushWhenNoDuplicate(Request request, Task task) {\n        logger.trace(\"Remaining capacity: {}\", this.queue.remainingCapacity());\n\n        try {\n            queue.put(request);\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    @Override\n    public Request poll(Task task) {\n        return queue.poll();\n    }\n\n    @Override\n    public int getLeftRequestsCount(Task task) {\n        return queue.size();\n    }\n\n    @Override\n    public int getTotalRequestsCount(Task task) {\n        return getDuplicateRemover().getTotalRequestsCount(task);\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/Scheduler.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\n\n/**\n * Scheduler is the part of url management.<br>\n * You can implement interface Scheduler to do:\n * manage urls to fetch\n * remove duplicate urls\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic interface Scheduler {\n\n    /**\n     * add a url to fetch\n     *\n     * @param request request\n     * @param task task\n     */\n    public void push(Request request, Task task);\n\n    /**\n     * get an url to crawl\n     *\n     * @param task the task of spider\n     * @return the url to crawl\n     */\n    public Request poll(Task task);\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/component/DuplicateRemover.java",
    "content": "package us.codecraft.webmagic.scheduler.component;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\n\n/**\n * Remove duplicate requests.\n * @author code4crafer@gmail.com\n * @since 0.5.1\n */\npublic interface DuplicateRemover {\n    /**\n     *\n     * Check whether the request is duplicate.\n     *\n     * @param request request\n     * @param task task\n     * @return true if is duplicate\n     */\n    public boolean isDuplicate(Request request, Task task);\n\n    /**\n     * Reset duplicate check.\n     * @param task task\n     */\n    public void resetDuplicateCheck(Task task);\n\n    /**\n     * Get TotalRequestsCount for monitor.\n     * @param task task\n     * @return number of total request\n     */\n    public int getTotalRequestsCount(Task task);\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/component/HashSetDuplicateRemover.java",
    "content": "package us.codecraft.webmagic.scheduler.component;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\n\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class HashSetDuplicateRemover implements DuplicateRemover {\n\n    private Set<String> urls = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());\n\n    @Override\n    public boolean isDuplicate(Request request, Task task) {\n        return !urls.add(getUrl(request));\n    }\n\n    protected String getUrl(Request request) {\n        return request.getUrl();\n    }\n\n    @Override\n    public void resetDuplicateCheck(Task task) {\n        urls.clear();\n    }\n\n    @Override\n    public int getTotalRequestsCount(Task task) {\n        return urls.size();\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/component/package.html",
    "content": "<html>\n\t<body>\nComponent of scheduler.\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/scheduler/package.html",
    "content": "<html>\n\t<body>\nScheduler is the part of url management.\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/AbstractSelectable.java",
    "content": "package us.codecraft.webmagic.selector;\n\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.commons.collections4.CollectionUtils;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.2\n */\npublic abstract class AbstractSelectable implements Selectable {\n\n    protected abstract List<String> getSourceTexts();\n\n    @Override\n    public Selectable css(String selector) {\n        return $(selector);\n    }\n\n    @Override\n    public Selectable css(String selector, String attrName) {\n        return $(selector, attrName);\n    }\n\n    protected Selectable select(Selector selector, List<String> strings) {\n        List<String> results = new ArrayList<String>();\n        for (String string : strings) {\n            String result = selector.select(string);\n            if (result != null) {\n                results.add(result);\n            }\n        }\n        return new PlainText(results);\n    }\n\n    protected Selectable selectList(Selector selector, List<String> strings) {\n        List<String> results = new ArrayList<String>();\n        for (String string : strings) {\n            List<String> result = selector.selectList(string);\n            results.addAll(result);\n        }\n        return new PlainText(results);\n    }\n\n    @Override\n    public List<String> all() {\n        return getSourceTexts();\n    }\n\n    @Override\n    public Selectable jsonPath(String jsonPath) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public String get() {\n    \tList<String> sourceTexts = all();\n        if (CollectionUtils.isNotEmpty(sourceTexts)) {\n            return sourceTexts.get(0);\n        } \n        return null;\n        \n    }\n\n    @Override\n    public Selectable select(Selector selector) {\n        return select(selector, getSourceTexts());\n    }\n\n    @Override\n    public Selectable selectList(Selector selector) {\n        return selectList(selector, getSourceTexts());\n    }\n\n    @Override\n    public Selectable regex(String regex) {\n        RegexSelector regexSelector = Selectors.regex(regex);\n        return selectList(regexSelector, getSourceTexts());\n    }\n\n    @Override\n    public Selectable regex(String regex, int group) {\n        RegexSelector regexSelector = Selectors.regex(regex, group);\n        return selectList(regexSelector, getSourceTexts());\n    }\n\n    @Override\n    public Selectable replace(String regex, String replacement) {\n        ReplaceSelector replaceSelector = new ReplaceSelector(regex,replacement);\n        return select(replaceSelector, getSourceTexts());\n    }\n\n    public String getFirstSourceText() {\n    \tList<String> sourceTexts = getSourceTexts();\n        if (CollectionUtils.isNotEmpty(sourceTexts)) {\n            return sourceTexts.get(0);\n        }\n        return null;\n    }\n\n    @Override\n    public String toString() {\n        return get();\n    }\n\n    @Override\n    public boolean match() {\n        return CollectionUtils.isNotEmpty(getSourceTexts());\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/AndSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * All selectors will be arranged as a pipeline. <br>\n * The next selector uses the result of the previous as source.\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class AndSelector implements Selector {\n\n    private List<Selector> selectors = new ArrayList<Selector>();\n\n    public AndSelector(Selector... selectors) {\n        for (Selector selector : selectors) {\n            this.selectors.add(selector);\n        }\n    }\n\n    public AndSelector(List<Selector> selectors) {\n        this.selectors = selectors;\n    }\n\n    @Override\n    public String select(String text) {\n        for (Selector selector : selectors) {\n            if (text == null) {\n                return null;\n            }\n            text = selector.select(text);\n        }\n        return text;\n    }\n\n    @Override\n    public List<String> selectList(String text) {\n        List<String> results = new ArrayList<String>();\n        boolean first = true;\n        for (Selector selector : selectors) {\n            if (first) {\n                results = selector.selectList(text);\n                first = false;\n            } else {\n                List<String> resultsTemp = new ArrayList<String>();\n                for (String result : results) {\n                    resultsTemp.addAll(selector.selectList(result));\n                }\n                results = resultsTemp;\n                if (results == null || results.size() == 0) {\n                    return results;\n                }\n            }\n        }\n        return results;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/BaseElementSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport us.codecraft.webmagic.utils.BaseSelectorUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.3.0\n */\npublic abstract class BaseElementSelector implements Selector, ElementSelector {\n    private Document parse(String text) {\n        // Jsoup could not parse <tr></tr> or <td></td> tag directly\n        // https://stackoverflow.com/questions/63607740/jsoup-couldnt-parse-tr-tag\n        text = BaseSelectorUtils.preParse(text);\n        return Jsoup.parse(text);\n    }\n\n    @Override\n    public String select(String text) {\n        if (text != null) {\n            return select(parse(text));\n        }\n        return null;\n    }\n\n    @Override\n    public List<String> selectList(String text) {\n        if (text != null) {\n            return selectList(parse(text));\n        } else {\n            return new ArrayList<String>();\n        }\n    }\n\n    public Element selectElement(String text) {\n        if (text != null) {\n            return selectElement(parse(text));\n        }\n        return null;\n    }\n\n    public List<Element> selectElements(String text) {\n        if (text != null) {\n            return selectElements(parse(text));\n        } else {\n            return new ArrayList<Element>();\n        }\n    }\n\n    public abstract Element selectElement(Element element);\n\n    public abstract List<Element> selectElements(Element element);\n\n    public abstract boolean hasAttribute();\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/CssSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.select.Elements;\n\n/**\n * CSS selector. Based on Jsoup.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class CssSelector extends BaseElementSelector {\n\n    private String selectorText;\n\n    private String attrName;\n\n    public CssSelector(String selectorText) {\n        this.selectorText = selectorText;\n    }\n\n    public CssSelector(String selectorText, String attrName) {\n        this.selectorText = selectorText;\n        this.attrName = attrName;\n    }\n\n    private String getValue(Element element) {\n        if (attrName == null) {\n            return element.outerHtml();\n        } else if (\"innerHtml\".equalsIgnoreCase(attrName)) {\n            return element.html();\n        } else if (\"text\".equalsIgnoreCase(attrName)) {\n            return getText(element);\n        } else if (\"allText\".equalsIgnoreCase(attrName)) {\n            return element.text();\n        } else {\n            return element.attr(attrName);\n        }\n    }\n\n    protected String getText(Element element) {\n        StringBuilder accum = new StringBuilder();\n        for (Node node : element.childNodes()) {\n            if (node instanceof TextNode) {\n                TextNode textNode = (TextNode) node;\n                accum.append(textNode.text());\n            }\n        }\n        return accum.toString();\n    }\n\n    @Override\n    public String select(Element element) {\n        List<Element> elements = selectElements(element);\n        if (CollectionUtils.isEmpty(elements)) {\n            return null;\n        }\n        return getValue(elements.get(0));\n    }\n\n    @Override\n    public List<String> selectList(Element doc) {\n        List<String> strings = new ArrayList<String>();\n        List<Element> elements = selectElements(doc);\n        if (CollectionUtils.isNotEmpty(elements)) {\n            for (Element element : elements) {\n                String value = getValue(element);\n                if (value != null) {\n                    strings.add(value);\n                }\n            }\n        }\n        return strings;\n    }\n\n    @Override\n    public Element selectElement(Element element) {\n        Elements elements = element.select(selectorText);\n        if (CollectionUtils.isNotEmpty(elements)) {\n            return elements.get(0);\n        }\n        return null;\n    }\n\n    @Override\n    public List<Element> selectElements(Element element) {\n        return element.select(selectorText);\n    }\n\n    @Override\n    public boolean hasAttribute() {\n        return attrName != null;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/ElementSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.jsoup.nodes.Element;\n\nimport java.util.List;\n\n/**\n * Selector(extractor) for html elements.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.3.0\n */\npublic interface ElementSelector {\n\n    /**\n     * Extract single result in text.<br>\n     * If there are more than one result, only the first will be chosen.\n     *\n     * @param element element\n     * @return result\n     */\n    public String select(Element element);\n\n    /**\n     * Extract all results in text.<br>\n     *\n     * @param element element\n     * @return results\n     */\n    public List<String> selectList(Element element);\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/Html.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Selectable html.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class Html extends HtmlNode {\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n\t/**\n\t * Disable jsoup html entity escape. It can be set just before any Html instance is created.\n     * @deprecated\n\t */\n\tpublic static boolean DISABLE_HTML_ENTITY_ESCAPE = false;\n\n    /**\n     * Store parsed document for better performance when only one text exist.\n     */\n    private Document document;\n\n    public Html(String text, String url) {\n        try {\n            this.document = Jsoup.parse(text, url);\n        } catch (Exception e) {\n            this.document = null;\n            logger.warn(\"parse document error \", e);\n        }\n    }\n\n    public Html(String text) {\n        try {\n            this.document = Jsoup.parse(text);\n        } catch (Exception e) {\n            this.document = null;\n            logger.warn(\"parse document error \", e);\n        }\n    }\n\n    public Html(Document document) {\n        this.document = document;\n    }\n\n    public Document getDocument() {\n        return document;\n    }\n\n    @Override\n    protected List<Element> getElements() {\n        return Collections.<Element>singletonList(getDocument());\n    }\n\n    /**\n     * @param selector selector\n     * @return result\n     */\n    public String selectDocument(Selector selector) {\n        if (selector instanceof ElementSelector) {\n            ElementSelector elementSelector = (ElementSelector) selector;\n            return elementSelector.select(getDocument());\n        } else {\n            return selector.select(getFirstSourceText());\n        }\n    }\n\n    public List<String> selectDocumentForList(Selector selector) {\n        if (selector instanceof ElementSelector) {\n            ElementSelector elementSelector = (ElementSelector) selector;\n            return elementSelector.selectList(getDocument());\n        } else {\n            return selector.selectList(getFirstSourceText());\n        }\n    }\n\n    public static Html create(String text) {\n        return new Html(text);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/HtmlNode.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.ListIterator;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class HtmlNode extends AbstractSelectable {\n\n    private final List<Element> elements;\n\n    public HtmlNode(List<Element> elements) {\n        this.elements = elements;\n    }\n\n    public HtmlNode() {\n        elements = null;\n    }\n\n    protected List<Element> getElements() {\n        return elements;\n    }\n\n    public Selectable smartContent() {\n        SmartContentSelector smartContentSelector = Selectors.smartContent();\n        return select(smartContentSelector, getSourceTexts());\n    }\n\n    public Selectable smartContent(int threshold) {\n        SmartContentSelector smartContentSelector = Selectors.smartContent(threshold);\n        return select(smartContentSelector, getSourceTexts());\n    }\n\n    @Override\n    public Selectable links() {\n        return selectElements(new LinksSelector());\n    }\n\n    @Override\n    public Selectable xpath(String xpath) {\n        XpathSelector xpathSelector = Selectors.xpath(xpath);\n        return selectElements(xpathSelector);\n    }\n\n    @Override\n    public Selectable selectList(Selector selector) {\n        if (selector instanceof BaseElementSelector) {\n           return selectElements((BaseElementSelector) selector);\n        }\n        return selectList(selector, getSourceTexts());\n    }\n\n    @Override\n    public Selectable select(Selector selector) {\n        return selectList(selector);\n    }\n\n    /**\n     * select elements\n     *\n     * @param elementSelector elementSelector\n     * @return result\n     */\n    protected Selectable selectElements(BaseElementSelector elementSelector) {\n        ListIterator<Element> elementIterator = getElements().listIterator();\n        if (!elementSelector.hasAttribute()) {\n            List<Element> resultElements = new ArrayList<Element>();\n            while (elementIterator.hasNext()) {\n                Element element = checkElementAndConvert(elementIterator);\n                List<Element> selectElements = elementSelector.selectElements(element);\n                resultElements.addAll(selectElements);\n            }\n            return new HtmlNode(resultElements);\n        } else {\n            // has attribute, consider as plaintext\n            List<String> resultStrings = new ArrayList<String>();\n            while (elementIterator.hasNext()) {\n                Element element = checkElementAndConvert(elementIterator);\n                List<String> selectList = elementSelector.selectList(element);\n                resultStrings.addAll(selectList);\n            }\n            return new PlainText(resultStrings);\n\n        }\n    }\n\n    /**\n     * Only document can be select\n     * See: https://github.com/code4craft/webmagic/issues/113\n     *\n     * @param elementIterator elementIterator\n     * @return element element\n     */\n    private Element checkElementAndConvert(ListIterator<Element> elementIterator) {\n        Element element = elementIterator.next();\n        if (!(element instanceof Document)) {\n            Document root = new Document(element.ownerDocument().baseUri());\n            Element clone = element.clone();\n            root.appendChild(clone);\n            elementIterator.set(root);\n            return root;\n        }\n        return element;\n    }\n\n    @Override\n    public Selectable $(String selector) {\n        CssSelector cssSelector = Selectors.$(selector);\n        return selectElements(cssSelector);\n    }\n\n    @Override\n    public Selectable $(String selector, String attrName) {\n        CssSelector cssSelector = Selectors.$(selector, attrName);\n        return selectElements(cssSelector);\n    }\n\n    @Override\n    public List<Selectable> nodes() {\n        List<Selectable> selectables = new ArrayList<Selectable>();\n        for (Element element : getElements()) {\n            List<Element> childElements = new ArrayList<Element>(1);\n            childElements.add(element);\n            selectables.add(new HtmlNode(childElements));\n        }\n        return selectables;\n    }\n\n    @Override\n    protected List<String> getSourceTexts() {\n        List<String> sourceTexts = new ArrayList<String>(getElements().size());\n        for (Element element : getElements()) {\n            sourceTexts.add(element.toString());\n        }\n        return sourceTexts;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/Json.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport com.alibaba.fastjson.JSON;\nimport us.codecraft.xsoup.XTokenQueue;\n\nimport java.util.List;\n\n/**\n * parse json\n * @author code4crafter@gmail.com\n * @since 0.5.0\n */\npublic class Json extends PlainText {\n\n    public Json(List<String> strings) {\n        super(strings);\n    }\n\n    public Json(String text) {\n        super(text);\n    }\n\n    /**\n     * remove padding for JSONP\n     * @param padding padding\n     * @return json after padding removed\n     */\n    public Json removePadding(String padding) {\n        String text = getFirstSourceText();\n        XTokenQueue tokenQueue = new XTokenQueue(text);\n        tokenQueue.consumeWhitespace();\n        tokenQueue.consume(padding);\n        tokenQueue.consumeWhitespace();\n        String chompBalanced = tokenQueue.chompBalancedNotInQuotes('(', ')');\n        return new Json(chompBalanced);\n    }\n\n    public <T> T toObject(Class<T> clazz) {\n        if (getFirstSourceText() == null) {\n            return null;\n        }\n        return JSON.parseObject(getFirstSourceText(), clazz);\n    }\n\n    public <T> List<T> toList(Class<T> clazz) {\n        if (getFirstSourceText() == null) {\n            return null;\n        }\n        return JSON.parseArray(getFirstSourceText(), clazz);\n    }\n\n    @Override\n    public Selectable jsonPath(String jsonPath) {\n        JsonPathSelector jsonPathSelector = new JsonPathSelector(jsonPath);\n        return selectList(jsonPathSelector,getSourceTexts());\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/JsonPathSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport com.alibaba.fastjson.JSON;\nimport com.jayway.jsonpath.JsonPath;\n\n/**\n * JsonPath selector.<br>\n * Used to extract content from JSON.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.1\n */\npublic class JsonPathSelector implements Selector {\n\n    private final String jsonPathStr;\n\n    private final JsonPath jsonPath;\n\n    public JsonPathSelector(String jsonPathStr) {\n        this.jsonPathStr = jsonPathStr;\n        this.jsonPath = JsonPath.compile(this.jsonPathStr);\n    }\n\n    @SuppressWarnings(\"unused\")\n    public String getJsonPathStr() {\n        return jsonPathStr;\n    }\n\n    @Override\n    public String select(String text) {\n        Object object = jsonPath.read(text);\n        if (object == null) {\n            return null;\n        }\n        if (object instanceof List) {\n            List<?> list = (List<?>) object;\n            if (list.size() > 0) {\n                return toString(list.iterator().next());\n            }\n        }\n        return object.toString();\n    }\n\n    private String toString(Object object) {\n        if (object instanceof Map) {\n            return JSON.toJSONString(object);\n        } else {\n            return String.valueOf(object);\n        }\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public List<String> selectList(String text) {\n        List<String> list = new ArrayList<>();\n        Object object = jsonPath.read(text);\n        if (object == null) {\n            return list;\n        }\n        if (object instanceof List) {\n            List<Object> items = (List<Object>) object;\n            for (Object item : items) {\n                list.add(toString(item));\n            }\n        } else {\n            list.add(toString(object));\n        }\n        return list;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/LinksSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\n\n/**\n * Links selector based on jsoup. Use absolute url. <br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.7.0\n */\npublic class LinksSelector extends BaseElementSelector {\n\n    @Override\n    public String select(Element element) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public List<String> selectList(Element element) {\n        Elements elements = element.select(\"a\");\n        List<String> links = new ArrayList<>(elements.size());\n        for (Element element0 : elements) {\n            if (StringUtils.isNotBlank(element0.baseUri())) {\n                links.add(element0.attr(\"abs:href\"));\n            } else {\n                links.add(element0.attr(\"href\"));\n            }\n        }\n        return links;\n    }\n\n    @Override\n    public Element selectElement(Element element) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public List<Element> selectElements(Element element) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean hasAttribute() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/OrSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * All extractors will do extracting separately, <br>\n * and the results of extractors will combined as the final result.\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class OrSelector implements Selector {\n\n    private List<Selector> selectors = new ArrayList<Selector>();\n\n    public OrSelector(Selector... selectors) {\n        for (Selector selector : selectors) {\n            this.selectors.add(selector);\n        }\n    }\n\n    public OrSelector(List<Selector> selectors) {\n        this.selectors = selectors;\n    }\n\n    @Override\n    public String select(String text) {\n        for (Selector selector : selectors) {\n            String result = selector.select(text);\n            if (result != null) {\n                return result;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public List<String> selectList(String text) {\n        List<String> results = new ArrayList<String>();\n        for (Selector selector : selectors) {\n            List<String> strings = selector.selectList(text);\n            results.addAll(strings);\n        }\n        return results;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/PlainText.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Selectable plain text.<br>\n * Can not be selected by XPath or CSS Selector.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class PlainText extends AbstractSelectable {\n\n    protected List<String> sourceTexts;\n\n    public PlainText(List<String> sourceTexts) {\n        this.sourceTexts = sourceTexts;\n    }\n\n    public PlainText(String text) {\n        this.sourceTexts = new ArrayList<String>();\n        sourceTexts.add(text);\n    }\n\n    public static PlainText create(String text) {\n        return new PlainText(text);\n    }\n\n    @Override\n    public Selectable xpath(String xpath) {\n        throw new UnsupportedOperationException(\"XPath can not apply to plain text. Please check whether you use a previous xpath with attribute select (/@href etc).\");\n    }\n\n    @Override\n    public Selectable $(String selector) {\n\t\tthrow new UnsupportedOperationException(\"$ can not apply to plain text. Please check whether you use a previous xpath with attribute select (/@href etc).\");\n    }\n\n    @Override\n    public Selectable $(String selector, String attrName) {\n\t\tthrow new UnsupportedOperationException(\"$ can not apply to plain text. Please check whether you use a previous xpath with attribute select (/@href etc).\");\n    }\n\n    @Override\n    public Selectable links() {\n\t\tthrow new UnsupportedOperationException(\"Links can not apply to plain text. Please check whether you use a previous xpath with attribute select (/@href etc).\");\n    }\n\n    @Override\n    public List<Selectable> nodes() {\n        List<Selectable> nodes = new ArrayList<Selectable>(getSourceTexts().size());\n        for (String string : getSourceTexts()) {\n            nodes.add(PlainText.create(string));\n        }\n        return nodes;\n    }\n\n    @Override\n    protected List<String> getSourceTexts() {\n        return sourceTexts;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/RegexResult.java",
    "content": "package us.codecraft.webmagic.selector;\n\n/**\n * Object contains regex results.<br>\n * For multi group result extension.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\nclass RegexResult {\n\n    private String[] groups;\n\n    public static final RegexResult EMPTY_RESULT = new RegexResult();\n\n    public RegexResult() {\n\n    }\n\n    public RegexResult(String[] groups) {\n        this.groups = groups;\n    }\n\n    public String get(int groupId) {\n        if (groups == null) {\n            return null;\n        }\n        return groups[groupId];\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/RegexSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.regex.PatternSyntaxException;\n\n/**\n * Selector in regex.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class RegexSelector implements Selector {\n\n    private String regexStr;\n\n    private Pattern regex;\n\n    private int group = 1;\n\n    public RegexSelector(String regexStr, int group) {\n        this.compileRegex(regexStr);\n        this.group = group;\n    }\n\n    private void compileRegex(String regexStr) {\n        if (StringUtils.isBlank(regexStr)) {\n            throw new IllegalArgumentException(\"regex must not be empty\");\n        }\n        try {\n            this.regex = Pattern.compile(regexStr, Pattern.DOTALL | Pattern.CASE_INSENSITIVE);\n            this.regexStr = regexStr;\n        } catch (PatternSyntaxException e) {\n            throw new IllegalArgumentException(\"invalid regex \"+regexStr, e);\n        }\n    }\n\n    /**\n     * Create a RegexSelector. When there is no capture group, the value is set to 0 else set to 1.\n     * @param regexStr the regular expression.\n     */\n    public RegexSelector(String regexStr) {\n        this.compileRegex(regexStr);\n        if (regex.matcher(\"\").groupCount() == 0) {\n            this.group = 0;\n        } else {\n            this.group = 1;\n        }\n    }\n\n    @Override\n    public String select(String text) {\n        return selectGroup(text).get(group);\n    }\n\n    @Override\n    public List<String> selectList(String text) {\n        List<String> strings = new ArrayList<String>();\n        List<RegexResult> results = selectGroupList(text);\n        for (RegexResult result : results) {\n            strings.add(result.get(group));\n        }\n        return strings;\n    }\n\n    public RegexResult selectGroup(String text) {\n        Matcher matcher = regex.matcher(text);\n        if (matcher.find()) {\n            String[] groups = new String[matcher.groupCount() + 1];\n            for (int i = 0; i < groups.length; i++) {\n                groups[i] = matcher.group(i);\n            }\n            return new RegexResult(groups);\n        }\n        return RegexResult.EMPTY_RESULT;\n    }\n\n    public List<RegexResult> selectGroupList(String text) {\n        Matcher matcher = regex.matcher(text);\n        List<RegexResult> resultList = new ArrayList<RegexResult>();\n        while (matcher.find()) {\n            String[] groups = new String[matcher.groupCount() + 1];\n            for (int i = 0; i < groups.length; i++) {\n                groups[i] = matcher.group(i);\n            }\n            resultList.add(new RegexResult(groups));\n        }\n        return resultList;\n    }\n\n    @Override\n    public String toString() {\n        return regexStr;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/ReplaceSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.regex.PatternSyntaxException;\n\n/**\n * Replace selector.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class ReplaceSelector implements Selector {\n\n    private String regexStr;\n\n    private String replacement;\n\n    private Pattern regex;\n\n    public ReplaceSelector(String regexStr, String replacement) {\n        this.regexStr = regexStr;\n        this.replacement = replacement;\n        try {\n            regex = Pattern.compile(regexStr);\n        } catch (PatternSyntaxException e) {\n            throw new IllegalArgumentException(\"invalid regex\", e);\n        }\n    }\n\n    @Override\n    public String select(String text) {\n        Matcher matcher = regex.matcher(text);\n        return matcher.replaceAll(replacement);\n    }\n\n    @Override\n    public List<String> selectList(String text) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public String toString() {\n        return regexStr + \"_\" + replacement;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/Selectable.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.List;\n\n/**\n * Selectable text.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic interface Selectable {\n\n    /**\n     * select list with xpath\n     *\n     * @param xpath xpath\n     * @return new Selectable after extract\n     */\n    public Selectable xpath(String xpath);\n\n    /**\n     * select list with css selector\n     *\n     * @param selector css selector expression\n     * @return new Selectable after extract\n     */\n    public Selectable $(String selector);\n\n    /**\n     * select list with css selector\n     *\n     * @param selector css selector expression\n     * @param attrName attribute name of css selector\n     * @return new Selectable after extract\n     */\n    public Selectable $(String selector, String attrName);\n\n    /**\n     * select list with css selector\n     *\n     * @param selector css selector expression\n     * @return new Selectable after extract\n     */\n    public Selectable css(String selector);\n\n    /**\n     * select list with css selector\n     *\n     * @param selector css selector expression\n     * @param attrName attribute name of css selector\n     * @return new Selectable after extract\n     */\n    public Selectable css(String selector, String attrName);\n    /**\n     * select all links\n     *\n     * @return all links\n     */\n    public Selectable links();\n\n    /**\n     * select list with regex, default group is group 1\n     *\n     * @param regex regex\n     * @return new Selectable after extract\n     */\n    public Selectable regex(String regex);\n\n    /**\n     * select list with regex\n     *\n     * @param regex regex\n     * @param group group\n     * @return new Selectable after extract\n     */\n    public Selectable regex(String regex, int group);\n\n    /**\n     * replace with regex\n     *\n     * @param regex regex\n     * @param replacement replacement\n     * @return new Selectable after extract\n     */\n    public Selectable replace(String regex, String replacement);\n\n    /**\n     * single string result\n     *\n     * @return single string result\n     */\n    public String toString();\n\n    /**\n     * single string result\n     *\n     * @return single string result\n     */\n    public String get();\n\n    /**\n     * if result exist for select\n     *\n     * @return true if result exist\n     */\n    public boolean match();\n\n    /**\n     * multi string result\n     *\n     * @return multi string result\n     */\n    public List<String> all();\n\n    /**\n     * extract by JSON Path expression\n     *\n     * @param jsonPath jsonPath\n     * @return result\n     */\n    public Selectable jsonPath(String jsonPath);\n\n    /**\n     * extract by custom selector\n     *\n     * @param selector selector\n     * @return result\n     */\n    public Selectable select(Selector selector);\n\n    /**\n     * extract by custom selector\n     *\n     * @param selector selector\n     * @return result\n     */\n    public Selectable selectList(Selector selector);\n\n    /**\n     * get all nodes\n     * @return result\n     */\n    public List<Selectable> nodes();\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/Selector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.List;\n\n/**\n * Selector(extractor) for text.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic interface Selector {\n\n    /**\n     * Extract single result in text.<br>\n     * If there are more than one result, only the first will be chosen.\n     *\n     * @param text text\n     * @return result\n     */\n    public String select(String text);\n\n    /**\n     * Extract all results in text.<br>\n     *\n     * @param text text\n     * @return results\n     */\n    public List<String> selectList(String text);\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/Selectors.java",
    "content": "package us.codecraft.webmagic.selector;\n\n/**\n * Convenient methods for selectors.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.1\n */\npublic abstract class Selectors {\n\n    public static RegexSelector regex(String expr) {\n        return new RegexSelector(expr);\n    }\n\n    public static RegexSelector regex(String expr, int group) {\n        return new RegexSelector(expr,group);\n    }\n\n    public static SmartContentSelector smartContent() {\n        return new SmartContentSelector();\n    }\n\n    public static SmartContentSelector smartContent(int threshold) {\n        return new SmartContentSelector(threshold);\n    }\n\n    public static CssSelector $(String expr) {\n        return new CssSelector(expr);\n    }\n\n    public static CssSelector $(String expr, String attrName) {\n        return new CssSelector(expr, attrName);\n    }\n\n    public static XpathSelector xpath(String expr) {\n        return new XpathSelector(expr);\n    }\n\n    /**\n     * @see #xpath(String)\n     * @param expr expr\n     * @return new selector\n     */\n    @Deprecated\n    public static XpathSelector xsoup(String expr) {\n        return new XpathSelector(expr);\n    }\n\n    public static AndSelector and(Selector... selectors) {\n        return new AndSelector(selectors);\n    }\n\n    public static OrSelector or(Selector... selectors) {\n        return new OrSelector(selectors);\n    }\n\n}"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/SmartContentSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport us.codecraft.webmagic.utils.Experimental;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Borrowed from https://code.google.com/p/cx-extractor/\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.4.1\n *\n */\n@Experimental\npublic class SmartContentSelector implements Selector {\n\n    private int threshold = 86;\n\n    public SmartContentSelector() {\n    }\n\n    public SmartContentSelector(int threshold) {\n        this.threshold = threshold;\n    }\n\n    @Override\n    public String select(String html) {\n        html = html.replaceAll(\"(?is)<!DOCTYPE.*?>\", \"\");\n        html = html.replaceAll(\"(?is)<!--.*?-->\", \"\");\t\t\t\t// remove html comment\n        html = html.replaceAll(\"(?is)<script.*?>.*?</script>\", \"\"); // remove javascript\n        html = html.replaceAll(\"(?is)<style.*?>.*?</style>\", \"\");   // remove css\n        html = html.replaceAll(\"&.{2,5};|&#.{2,5};\", \" \");\t\t\t// remove special char\n        html = html.replaceAll(\"(?is)<.*?>\", \"\");\n        List<String> lines;\n        int blocksWidth =3;\n        int start;\n        int end;\n        StringBuilder text = new StringBuilder();\n        ArrayList<Integer> indexDistribution = new ArrayList<Integer>();\n\n        lines = Arrays.asList(html.split(\"\\n\"));\n\n        for (int i = 0; i < lines.size() - blocksWidth; i++) {\n            int wordsNum = 0;\n            for (int j = i; j < i + blocksWidth; j++) {\n                lines.set(j, lines.get(j).replaceAll(\"\\\\s+\", \"\"));\n                wordsNum += lines.get(j).length();\n            }\n            indexDistribution.add(wordsNum);\n        }\n\n        start = -1; end = -1;\n        boolean boolstart = false, boolend = false;\n        text.setLength(0);\n\n        for (int i = 0; i < indexDistribution.size() - 1; i++) {\n            if (indexDistribution.get(i) > threshold && ! boolstart) {\n                if (indexDistribution.get(i+1).intValue() != 0\n                        || indexDistribution.get(i+2).intValue() != 0\n                        || indexDistribution.get(i+3).intValue() != 0) {\n                    boolstart = true;\n                    start = i;\n                    continue;\n                }\n            }\n            if (boolstart) {\n                if (indexDistribution.get(i).intValue() == 0\n                        || indexDistribution.get(i+1).intValue() == 0) {\n                    end = i;\n                    boolend = true;\n                }\n            }\n            StringBuilder tmp = new StringBuilder();\n            if (boolend) {\n                //System.out.println(start+1 + \"\\t\\t\" + end+1);\n                for (int ii = start; ii <= end; ii++) {\n                    if (lines.get(ii).length() < 5) continue;\n                    tmp.append(lines.get(ii) + \"\\n\");\n                }\n                String str = tmp.toString();\n                //System.out.println(str);\n                if (str.contains(\"Copyright\")   ) continue;\n                text.append(str);\n                boolstart = boolend = false;\n            }\n        }\n        return text.toString();\n    }\n\n    @Override\n    public List<String> selectList(String text) {\n        throw new UnsupportedOperationException();\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/XpathSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\n\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.jsoup.nodes.Element;\nimport us.codecraft.xsoup.XPathEvaluator;\nimport us.codecraft.xsoup.Xsoup;\n\n/**\n * XPath selector based on Xsoup.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.3.0\n */\npublic class XpathSelector extends BaseElementSelector {\n\n    private XPathEvaluator xPathEvaluator;\n\n    public XpathSelector(String xpathStr) {\n        this.xPathEvaluator = Xsoup.compile(xpathStr);\n    }\n\n    @Override\n    public String select(Element element) {\n        return xPathEvaluator.evaluate(element).get();\n    }\n\n    @Override\n    public List<String> selectList(Element element) {\n        return xPathEvaluator.evaluate(element).list();\n    }\n\n    @Override\n    public Element selectElement(Element element) {\n        List<Element> elements = selectElements(element);\n        if (CollectionUtils.isNotEmpty(elements)){\n            return elements.get(0);\n        }\n        return null;\n    }\n\n    @Override\n    public List<Element> selectElements(Element element) {\n        return xPathEvaluator.evaluate(element).getElements();\n    }\n\n    @Override\n    public boolean hasAttribute() {\n        return xPathEvaluator.hasAttribute();\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/selector/package.html",
    "content": "<html>\n\t<body>\nSelectors for page extraction. Core API is the interface Selectable，and internal core is the interface Selector。\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/thread/CountableThreadPool.java",
    "content": "package us.codecraft.webmagic.thread;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.locks.Condition;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * Thread pool for workers.<br><br>\n * Use {@link java.util.concurrent.ExecutorService} as inner implement. <br><br>\n * New feature: <br><br>\n * 1. Block when thread pool is full to avoid poll many urls without process. <br><br>\n * 2. Count of thread alive for monitor.\n *\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic class CountableThreadPool {\n\n    private int threadNum;\n\n    private AtomicInteger threadAlive = new AtomicInteger();\n\n    private ReentrantLock reentrantLock = new ReentrantLock();\n\n    private Condition condition = reentrantLock.newCondition();\n\n    public CountableThreadPool(int threadNum) {\n        this.threadNum = threadNum;\n        this.executorService = Executors.newFixedThreadPool(threadNum);\n    }\n\n    public CountableThreadPool(int threadNum, ExecutorService executorService) {\n        this.threadNum = threadNum;\n        this.executorService = executorService;\n    }\n\n    public void setExecutorService(ExecutorService executorService) {\n        this.executorService = executorService;\n    }\n\n    public int getThreadAlive() {\n        return threadAlive.get();\n    }\n\n    public int getThreadNum() {\n        return threadNum;\n    }\n\n    private ExecutorService executorService;\n\n    public void execute(final Runnable runnable) {\n\n\n        if (threadAlive.get() >= threadNum) {\n            try {\n                reentrantLock.lock();\n                while (threadAlive.get() >= threadNum) {\n                    try {\n                        condition.await();\n                    } catch (InterruptedException e) {\n                    }\n                }\n            } finally {\n                reentrantLock.unlock();\n            }\n        }\n        threadAlive.incrementAndGet();\n        executorService.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    runnable.run();\n                } finally {\n                    try {\n                        reentrantLock.lock();\n                        threadAlive.decrementAndGet();\n                        condition.signal();\n                    } finally {\n                        reentrantLock.unlock();\n                    }\n                }\n            }\n        });\n    }\n\n    public boolean isShutdown() {\n        return executorService.isShutdown();\n    }\n\n    public void shutdown() {\n        executorService.shutdown();\n    }\n\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/BaseSelectorUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\n/**\n * @author hooy\n */\npublic class BaseSelectorUtils {\n\n    /**\n     * Jsoup/HtmlCleaner could not parse \"tr\" or \"td\" tag directly\n     * https://stackoverflow.com/questions/63607740/jsoup-couldnt-parse-tr-tag\n     *\n     * @param text - the html string\n     * @return text\n     */\n    public static String preParse(String text) {\n        if (((text.startsWith(\"<tr>\") || text.startsWith(\"<tr \")) && text.endsWith(\"</tr>\"))\n                || ((text.startsWith(\"<td>\") || text.startsWith(\"<td \")) && text.endsWith(\"</td>\"))) {\n            text = \"<table>\" + text + \"</table>\";\n        }\n        return text;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/CharsetUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/3/11\n *         Time: 10:36\n * @since 0.6.2\n */\npublic abstract class CharsetUtils {\n\n    private static Logger logger = LoggerFactory.getLogger(CharsetUtils.class);\n\n    private CharsetUtils() {\n        throw new AssertionError(\"No us.codecraft.webmagic.utils.CharsetUtils instances for you!\");\n    }\n\n    public static String detectCharset(String contentType, byte[] contentBytes) throws IOException {\n        String charset;\n        // charset\n        // 1、encoding in http header Content-Type\n        charset = UrlUtils.getCharset(contentType);\n        if (StringUtils.isNotBlank(contentType) && StringUtils.isNotBlank(charset)) {\n            logger.debug(\"Auto get charset: {}\", charset);\n            return charset;\n        }\n        // use default charset to decode first time\n        Charset defaultCharset = Charset.defaultCharset();\n        String content = new String(contentBytes, defaultCharset);\n        // 2、charset in meta\n        if (StringUtils.isNotEmpty(content)) {\n            Document document = Jsoup.parse(content);\n            Elements links = document.select(\"meta\");\n            for (Element link : links) {\n                // 2.1、html4.01 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n                String metaContent = link.attr(\"content\");\n                String metaCharset = link.attr(\"charset\");\n                if (metaContent.indexOf(\"charset\") != -1) {\n                    metaContent = metaContent.substring(metaContent.indexOf(\"charset\"), metaContent.length());\n                    charset = metaContent.split(\"=\")[1];\n                    break;\n                }\n                // 2.2、html5 <meta charset=\"UTF-8\" />\n                else if (StringUtils.isNotEmpty(metaCharset)) {\n                    charset = metaCharset;\n                    break;\n                }\n            }\n        }\n        logger.debug(\"Auto get charset: {}\", charset);\n        // 3、todo use tools as cpdetector for content decode\n        return charset;\n    }\n    \n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/Experimental.java",
    "content": "package us.codecraft.webmagic.utils;\n\n/**\n * Stands for features unstable.\n * @author code4crafter@gmail.com <br>\n */\npublic @interface Experimental {\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/FilePersistentBase.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport java.io.File;\n\n/**\n * Base object of file persistence.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class FilePersistentBase {\n\n    protected String path;\n\n    public static String PATH_SEPERATOR = \"/\";\n\n    static {\n        String property = System.getProperties().getProperty(\"file.separator\");\n        if (property != null) {\n            PATH_SEPERATOR = property;\n        }\n    }\n\n    public void setPath(String path) {\n        if (!path.endsWith(PATH_SEPERATOR)) {\n            path += PATH_SEPERATOR;\n        }\n        this.path = path;\n    }\n\n    public File getFile(String fullName) {\n        checkAndMakeParentDirecotry(fullName);\n        return new File(fullName);\n    }\n\n    public void checkAndMakeParentDirecotry(String fullName) {\n        int index = fullName.lastIndexOf(PATH_SEPERATOR);\n        if (index > 0) {\n            String path = fullName.substring(0, index);\n            File file = new File(path);\n            if (!file.exists()) {\n                file.mkdirs();\n            }\n        }\n    }\n\n    public String getPath() {\n        return path;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/HttpClientUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport org.apache.http.Header;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/3/27\n */\npublic abstract class HttpClientUtils {\n\n    public static Map<String,List<String>> convertHeaders(Header[] headers){\n        Map<String,List<String>> results = new HashMap<String, List<String>>();\n        for (Header header : headers) {\n            List<String> list = results.get(header.getName());\n            if (list == null) {\n                list = new ArrayList<String>();\n                results.put(header.getName(), list);\n            }\n            list.add(header.getValue());\n        }\n        return results;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/HttpConstant.java",
    "content": "package us.codecraft.webmagic.utils;\n\n/**\n * Some constants of Http protocal.\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic abstract class HttpConstant {\n\n    public static abstract class Method {\n\n        public static final String GET = \"GET\";\n\n        public static final String HEAD = \"HEAD\";\n\n        public static final String POST = \"POST\";\n\n        public static final String PUT = \"PUT\";\n\n        public static final String DELETE = \"DELETE\";\n\n        public static final String TRACE = \"TRACE\";\n\n        public static final String CONNECT = \"CONNECT\";\n\n    }\n\n    public static abstract class StatusCode {\n\n        public static final int CODE_200 = 200;\n\n    }\n\n    public static abstract class Header {\n\n        public static final String REFERER = \"Referer\";\n\n        public static final String USER_AGENT = \"User-Agent\";\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/NumberUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\n/**\n * @author yihua.huang@dianping.com\n */\npublic abstract class NumberUtils {\n\n    public static int compareLong(long o1, long o2) {\n    \treturn Long.compare(o1, o2);\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/ProxyUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.proxy.Proxy;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\n\n/**\n * Pooled Proxy Object\n * \n * @author yxssfxwzy@sina.com <br>\n * @since 0.5.1\n */\n\npublic class ProxyUtils {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(ProxyUtils.class);\n\n\tpublic static boolean validateProxy(Proxy p) {\n\t\tSocket socket = null;\n\t\ttry {\n\t\t\tsocket = new Socket();\n\t\t\tInetSocketAddress endpointSocketAddr = new InetSocketAddress(p.getHost(), p.getPort());\n\t\t\tsocket.connect(endpointSocketAddr, 3000);\n\t\t\treturn true;\n\t\t} catch (IOException e) {\n\t\t\tlogger.warn(\"FAILRE - CAN not connect!  remote: \" + p);\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tif (socket != null) {\n\t\t\t\ttry {\n\t\t\t\t\tsocket.close();\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\tlogger.warn(\"Error occurred while closing socket of validating proxy\", e);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/UrlUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport org.apache.commons.lang3.StringUtils;\nimport us.codecraft.webmagic.Request;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * url and html utils.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.1.0\n */\npublic class UrlUtils {\n\n    /**\n     * canonicalizeUrl\n     * <br>\n     * Borrowed from Jsoup.\n     *\n     * @param url url\n     * @param refer refer\n     * @return canonicalizeUrl\n     */\n    public static String canonicalizeUrl(String url, String refer) {\n        URL base;\n        try {\n            try {\n                base = new URL(refer);\n            } catch (MalformedURLException e) {\n                // the base is unsuitable, but the attribute may be abs on its own, so try that\n                URL abs = new URL(refer);\n                return abs.toExternalForm();\n            }\n            // workaround: java resolves '//path/file + ?foo' to '//path/?foo', not '//path/file?foo' as desired\n            if (url.startsWith(\"?\"))\n                url = base.getPath() + url;\n            URL abs = new URL(base, url);\n            return abs.toExternalForm();\n        } catch (MalformedURLException e) {\n            return \"\";\n        }\n    }\n\n    /**\n     *\n     * @param url url\n     * @return new url\n     * @deprecated\n     */\n    public static String encodeIllegalCharacterInUrl(String url) {\n        return url.replace(\" \", \"%20\");\n    }\n\n    public static String fixIllegalCharacterInUrl(String url) {\n        //TODO more charator support\n        return url.replace(\" \", \"%20\").replaceAll(\"#+\", \"#\");\n    }\n\n    public static String getHost(String url) {\n        String host = url;\n        int i = StringUtils.ordinalIndexOf(url, \"/\", 3);\n        if (i > 0) {\n            host = StringUtils.substring(url, 0, i);\n        }\n        return host;\n    }\n\n    private static Pattern patternForProtocal = Pattern.compile(\"[\\\\w]+://\");\n\n    public static String removeProtocol(String url) {\n        return patternForProtocal.matcher(url).replaceAll(\"\");\n    }\n\n    public static String getDomain(String url) {\n        String domain = removeProtocol(url);\n        int i = StringUtils.indexOf(domain, \"/\", 1);\n        if (i > 0) {\n            domain = StringUtils.substring(domain, 0, i);\n        }\n        return removePort(domain);\n    }\n\n    public static String removePort(String domain) {\n        int portIndex = domain.indexOf(\":\");\n        if (portIndex != -1) {\n            return domain.substring(0, portIndex);\n        }else {\n            return domain;\n        }\n    }\n\n    public static List<Request> convertToRequests(Collection<String> urls) {\n        List<Request> requestList = new ArrayList<Request>(urls.size());\n        for (String url : urls) {\n            requestList.add(new Request(url));\n        }\n        return requestList;\n    }\n\n    public static List<String> convertToUrls(Collection<Request> requests) {\n        List<String> urlList = new ArrayList<String>(requests.size());\n        for (Request request : requests) {\n            urlList.add(request.getUrl());\n        }\n        return urlList;\n    }\n\n    private static final Pattern patternForCharset = Pattern.compile(\"charset\\\\s*=\\\\s*['\\\"]*([^\\\\s;'\\\"]*)\", Pattern.CASE_INSENSITIVE);\n\n    public static String getCharset(String contentType) {\n        if (contentType == null) {\n            return null;\n        }\n\n        Matcher matcher = patternForCharset.matcher(contentType);\n        if (matcher.find()) {\n            String charset = matcher.group(1);\n            if (Charset.isSupported(charset)) {\n                return charset;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/WMCollections.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 16/12/18\n *         Time: 上午10:16\n */\npublic class WMCollections {\n\n    public static <T> Set<T> newHashSet(T... t){\n        Set<T> set = new HashSet<T>(t.length);\n        for (T t1 : t) {\n            set.add(t1);\n        }\n        return set;\n    }\n\n    public static <T> List<T> newArrayList(T... t){\n        List<T> list = new ArrayList<T>(t.length);\n        for (T t1 : t) {\n            list.add(t1);\n        }\n        return list;\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/main/java/us/codecraft/webmagic/utils/package.html",
    "content": "<html>\n\t<body>\nStatic utils of webmagic.\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/HtmlTest.java",
    "content": "package us.codecraft.webmagic;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.selector.Html;\nimport us.codecraft.webmagic.selector.Selectable;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-4-21\n * Time: 上午8:42\n */\npublic class HtmlTest {\n\n    @Test\n    public void testRegexSelector() {\n        Html selectable = new Html(\"aaaaaaab\");\n\t\tassertThat(selectable.regex(\"(a+b)\").replace(\"aa(a)\", \"$1bb\").toString()).isEqualTo(\"abbabbab\");\n    }\n\n\t@Ignore(\"not work in jsoup 1.8.x\")\n\t@Test\n\tpublic void testDisableJsoupHtmlEntityEscape() throws Exception {\n\t\tHtml.DISABLE_HTML_ENTITY_ESCAPE = true;\n\t\tHtml html = new Html(\"aaaaaaa&b\");\n\t\tassertThat(html.regex(\"(aaaaaaa&b)\").toString()).isEqualTo(\"aaaaaaa&b\");\n\t}\n\n\t@Test\n\tpublic void testEnableJsoupHtmlEntityEscape() throws Exception {\n\t\tHtml html = new Html(\"aaaaaaa&b\");\n\t\tassertThat(html.regex(\"(aaaaaaa&amp;b)\").toString()).isEqualTo(\"aaaaaaa&amp;b\");\n\t}\n\n\t@Test\n\tpublic void testAHrefExtract(){\n\t\tHtml html = new Html(\"<a data-tip=\\\"p$t$xxx\\\" href=\\\"/xx/xx\\\">xx</a>\");\n\t\tassertThat(html.links().all()).contains(\"/xx/xx\");\n\t}\n\n\t@Test\n\tpublic void testNthNodesGet(){\n\t\tHtml html = new Html(\"<a data-tip=\\\"p$t$xxx\\\" href=\\\"/xx/xx\\\">xx</a>\");\n\t\tassertThat(html.xpath(\"//a[1]/@href\").get()).isEqualTo(\"/xx/xx\");\n\t\tSelectable selectable = html.xpath(\"//a[1]\").nodes().get(0);\n\t\tassertThat(selectable.xpath(\"/a/@href\").get()).isEqualTo(\"/xx/xx\");\n\t}\n\n\t@Test\n\tpublic void testGetHrefsByJsoup(){\n\t\tHtml html = new Html(\"<html><a href='issues'>issues</a><img src='webmagic.jpg'/></html>\",\"https://github.com/code4craft/webmagic/\");\n\t\tassertThat(html.xpath(\"//a[1]/@abs:href\").get()).isEqualTo(\"https://github.com/code4craft/webmagic/issues\");\n\t\tassertThat(html.xpath(\"//img/@abs:src\").get()).isEqualTo(\"https://github.com/code4craft/webmagic/webmagic.jpg\");\n\t\thtml = new Html(\"<html><base href='https://github.com/code4craft/webmagic/'><a href='issues'>issues</a><img src='webmagic.jpg'/></base></html>\");\n\t\tassertThat(html.xpath(\"//a[1]/@abs:href\").get()).isEqualTo(\"https://github.com/code4craft/webmagic/issues\");\n\t\tassertThat(html.xpath(\"//img/@abs:src\").get()).isEqualTo(\"https://github.com/code4craft/webmagic/webmagic.jpg\");\n\t}\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/RequestTest.java",
    "content": "package us.codecraft.webmagic;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport us.codecraft.webmagic.utils.HttpConstant;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/3/11\n */\npublic class RequestTest {\n\n    @Test\n    public void testEqualsAndHashCode() throws Exception {\n        Request requestA = new Request(\"http://www.google.com/\");\n        Request requestB = new Request(\"http://www.google.com/\");\n        assertThat(requestA.hashCode()).isEqualTo(requestB.hashCode());\n        assertThat(requestA).isEqualTo(requestB);\n        requestA.setMethod(HttpConstant.Method.GET);\n        requestA.setMethod(HttpConstant.Method.POST);\n        assertThat(requestA).isNotEqualTo(requestB);\n        assertThat(requestA.hashCode()).isNotEqualTo(requestB.hashCode());\n    }\n\n    @Test\n    public void testSetExtras() {\n        Request request = new Request();\n        Map<String, Object> extras = Collections.singletonMap(\"a\", \"1\");\n        request.setExtras(extras);\n        request.putExtra(\"b\", \"2\");\n        assertThat(request.<String>getExtra(\"a\")).isEqualTo(\"1\");\n        assertThat(request.<String>getExtra(\"b\")).isEqualTo(\"2\");\n    }\n\n    @Test\n    public void testGetExtras() {\n        Request request = new Request();\n        request.putExtra(\"a\", \"1\");\n        assertThat(request.getExtras()).containsEntry(\"a\", \"1\");\n    }\n\n    @Test(expected = UnsupportedOperationException.class)\n    public void testGetExtrasShouldBeUnmodifiable() {\n        Request request = new Request();\n        request.getExtras().put(\"a\", \"1\");\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/ResultItemsTest.java",
    "content": "package us.codecraft.webmagic;\n\nimport org.junit.Test;\n\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class ResultItemsTest {\n\n    @Test\n    public void testOrderOfEntries() throws Exception {\n        ResultItems resultItems = new ResultItems();\n        resultItems.put(\"a\", \"a\");\n        resultItems.put(\"b\", \"b\");\n        resultItems.put(\"c\", \"c\");\n        assertThat(resultItems.getAll().keySet()).containsExactly(\"a\",\"b\",\"c\");\n\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/SiteTest.java",
    "content": "package us.codecraft.webmagic;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Test;\n\npublic class SiteTest {\n\n    @Test\n    public void test() {\n        Site site = Site.me().setDefaultCharset(StandardCharsets.UTF_8.name());\n        assertEquals(StandardCharsets.UTF_8.name(), site.getDefaultCharset());\n    }\n\n    @Test\n    public void addCookieTest(){\n        Site site=Site.me().setDefaultCharset(StandardCharsets.UTF_8.name());\n        site.addCookie(\"cookieDefault\",\"cookie-webmagicDefault\");\n        String firstDomain=\"example.com\";\n        String secondDomain=\"exampleCopy.com\";\n        site.addCookie(firstDomain, \"cookie\", \"cookie-webmagic\");\n        site.addCookie(firstDomain, \"cookieCopy\", \"cookie-webmagicCopy\");\n        site.addCookie(secondDomain, \"cookie\", \"cookie-webmagic\");\n        Map<String, Map<String, String>> allCookies = site.getAllCookies();\n        List<String> domains=new ArrayList<>();\n        for(String key : allCookies.keySet()){\n            domains.add(key);\n        }\n        assertEquals(\"cookie-webmagic\", allCookies.get(firstDomain).get(\"cookie\"));\n        assertEquals(\"cookie-webmagicCopy\", allCookies.get(firstDomain).get(\"cookieCopy\"));\n        assertEquals(\"cookie-webmagic\", allCookies.get(secondDomain).get(\"cookie\"));\n        assertEquals(2, domains.size());\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/SpiderTest.java",
    "content": "package us.codecraft.webmagic;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.downloader.Downloader;\nimport us.codecraft.webmagic.pipeline.Pipeline;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.processor.SimplePageProcessor;\nimport us.codecraft.webmagic.scheduler.Scheduler;\n\nimport java.util.Random;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class SpiderTest {\n\n    @Ignore(\"long time\")\n    @Test\n    public void testStartAndStop() throws InterruptedException {\n        Spider spider = Spider.create(new SimplePageProcessor( \"http://www.oschina.net/*\")).addPipeline(new Pipeline() {\n            @Override\n            public void process(ResultItems resultItems, Task task) {\n                System.out.println(1);\n            }\n        }).thread(1).addUrl(\"http://www.oschina.net/\");\n        spider.start();\n        Thread.sleep(10000);\n        spider.stop();\n        Thread.sleep(10000);\n        spider.start();\n        Thread.sleep(10000);\n    }\n\n    @Ignore(\"long time\")\n    @Test\n    public void testWaitAndNotify() throws InterruptedException {\n        for (int i = 0; i < 10000; i++) {\n            System.out.println(\"round \" + i);\n            testRound();\n        }\n    }\n\n    private void testRound() {\n        Spider spider = Spider.create(new PageProcessor() {\n\n            private AtomicInteger count = new AtomicInteger();\n\n            @Override\n            public void process(Page page) {\n                page.setSkip(true);\n            }\n\n            @Override\n            public Site getSite() {\n                return Site.me().setSleepTime(0);\n            }\n        }).setDownloader(new Downloader() {\n            @Override\n            public Page download(Request request, Task task) {\n                return new Page().setRawText(\"\");\n            }\n\n            @Override\n            public void setThread(int threadNum) {\n\n            }\n        }).setScheduler(new Scheduler() {\n\n            private AtomicInteger count = new AtomicInteger();\n\n            private Random random = new Random();\n\n            @Override\n            public void push(Request request, Task task) {\n\n            }\n\n            @Override\n            public synchronized Request poll(Task task) {\n                if (count.incrementAndGet() > 1000) {\n                    return null;\n                }\n                if (random.nextInt(100)>90){\n                    return null;\n                }\n                return new Request(\"test\");\n            }\n        }).thread(10);\n        spider.run();\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/downloader/HttpClientDownloaderTest.java",
    "content": "package us.codecraft.webmagic.downloader;\n\n\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.util.Map;\nimport org.apache.commons.collections4.map.HashedMap;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.apache.http.util.EntityUtils;\nimport org.junit.Test;\nimport com.github.dreamhead.moco.HttpServer;\nimport com.github.dreamhead.moco.Runnable;\nimport com.github.dreamhead.moco.Runner;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.model.HttpRequestBody;\nimport us.codecraft.webmagic.proxy.Proxy;\nimport us.codecraft.webmagic.proxy.SimpleProxyProvider;\nimport us.codecraft.webmagic.selector.Html;\nimport us.codecraft.webmagic.utils.CharsetUtils;\nimport us.codecraft.webmagic.utils.HttpConstant;\nimport static com.github.dreamhead.moco.Moco.and;\nimport static com.github.dreamhead.moco.Moco.by;\nimport static com.github.dreamhead.moco.Moco.cookie;\nimport static com.github.dreamhead.moco.Moco.eq;\nimport static com.github.dreamhead.moco.Moco.form;\nimport static com.github.dreamhead.moco.Moco.header;\nimport static com.github.dreamhead.moco.Moco.httpServer;\nimport static com.github.dreamhead.moco.Moco.method;\nimport static com.github.dreamhead.moco.Moco.not;\nimport static com.github.dreamhead.moco.Moco.query;\nimport static com.github.dreamhead.moco.Moco.text;\nimport static com.github.dreamhead.moco.Moco.uri;\nimport static com.github.dreamhead.moco.Moco.with;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.assertTrue;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class HttpClientDownloaderTest {\n\n    public static final String PAGE_ALWAYS_NOT_EXISTS = \"http://localhost:13423/404\";\n\n    @Test\n    public void testDownloader() {\n        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n        Html html = httpClientDownloader.download(\"https://www.baidu.com/\");\n        assertTrue(!html.getFirstSourceText().isEmpty());\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testDownloaderInIllegalUrl() throws UnsupportedEncodingException {\n        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n        httpClientDownloader.download(\"http://www.oschina.net/>\");\n    }\n\n    @Test\n    public void test_download_fail() {\n        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n        Task task = Site.me().setDomain(\"localhost\").setCycleRetryTimes(5).toTask();\n        Request request = new Request(PAGE_ALWAYS_NOT_EXISTS);\n        Page page = httpClientDownloader.download(request, task);\n        assertThat(page.isDownloadSuccess()).isFalse();\n    }\n\n    @Test\n    public void testGetHtmlCharset() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.get(by(uri(\"/header\"))).response(header(\"Content-Type\", \"text/html; charset=gbk\"));\n        server.get(by(uri(\"/meta4\"))).response(with(text(\"<html>\\n\" +\n                \"  <head>\\n\" +\n                \"    <meta charset='gbk'/>\\n\" +\n                \"  </head>\\n\" +\n                \"  <body></body>\\n\" +\n                \"</html>\")),header(\"Content-Type\",\"text/html; charset=gbk\"));\n        server.get(by(uri(\"/meta5\"))).response(with(text(\"<html>\\n\" +\n                \"  <head>\\n\" +\n                \"    <meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=gbk\\\" />\\n\" +\n                \"  </head>\\n\" +\n                \"  <body></body>\\n\" +\n                \"</html>\")),header(\"Content-Type\",\"text/html\"));\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() {\n                String charset = getCharsetByUrl(\"http://127.0.0.1:13423/header\");\n                assertEquals(charset, \"gbk\");\n                charset = getCharsetByUrl(\"http://127.0.0.1:13423/meta4\");\n                assertEquals(charset, \"gbk\");\n                charset = getCharsetByUrl(\"http://127.0.0.1:13423/meta5\");\n                assertEquals(charset, \"gbk\");\n            }\n\n            private String getCharsetByUrl(String url) {\n                HttpClientDownloader downloader = new HttpClientDownloader();\n                Site site = Site.me();\n                CloseableHttpClient httpClient = new HttpClientGenerator().getClient(site);\n                // encoding in http header Content-Type\n                Request requestGBK = new Request(url);\n                CloseableHttpResponse httpResponse = null;\n                try {\n                    httpResponse = httpClient.execute(new HttpUriRequestConverter().convert(requestGBK, site, null).getHttpUriRequest());\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n                String charset = null;\n                try {\n                    byte[] contentBytes = IOUtils.toByteArray(httpResponse.getEntity().getContent());\n                    charset = CharsetUtils.detectCharset(httpResponse.getEntity().getContentType().getValue(), contentBytes);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n                return charset;\n            }\n        });\n    }\n\n    @Test\n    public void test_selectRequestMethod() throws Exception {\n        final int port = 13423;\n        HttpServer server = httpServer(port);\n        server.get(eq(query(\"q\"), \"webmagic\")).response(\"get\");\n        server.post(eq(form(\"q\"), \"webmagic\")).response(\"post\");\n        server.put(eq(form(\"q\"), \"webmagic\")).response(\"put\");\n        server.delete(eq(query(\"q\"), \"webmagic\")).response(\"delete\");\n        server.request(and(by(method(\"HEAD\")),eq(query(\"q\"), \"webmagic\"))).response(header(\"method\",\"head\"));\n        server.request(and(by(method(\"TRACE\")),eq(query(\"q\"), \"webmagic\"))).response(\"trace\");\n        final HttpUriRequestConverter httpUriRequestConverter = new HttpUriRequestConverter();\n        final Site site = Site.me();\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                Request request = new Request();\n                request.setUrl(\"http://127.0.0.1:\" + port + \"/search?q=webmagic\");\n                request.setMethod(HttpConstant.Method.GET);\n                Map<String,Object> params = new HashedMap();\n                params.put(\"q\",\"webmagic\");\n                HttpUriRequest httpUriRequest = httpUriRequestConverter.convert(request,site,null).getHttpUriRequest();\n                assertThat(EntityUtils.toString(HttpClients.custom().build().execute(httpUriRequest).getEntity())).isEqualTo(\"get\");\n                request.setMethod(HttpConstant.Method.DELETE);\n                httpUriRequest = httpUriRequestConverter.convert(request, site, null).getHttpUriRequest();\n                assertThat(EntityUtils.toString(HttpClients.custom().build().execute(httpUriRequest).getEntity())).isEqualTo(\"delete\");\n                request.setMethod(HttpConstant.Method.HEAD);\n                httpUriRequest = httpUriRequestConverter.convert(request, site, null).getHttpUriRequest();\n                assertThat(HttpClients.custom().build().execute(httpUriRequest).getFirstHeader(\"method\").getValue()).isEqualTo(\"head\");\n                request.setMethod(HttpConstant.Method.TRACE);\n                httpUriRequest = httpUriRequestConverter.convert(request, site, null).getHttpUriRequest();\n                assertThat(EntityUtils.toString(HttpClients.custom().build().execute(httpUriRequest).getEntity())).isEqualTo(\"trace\");\n                request.setUrl(\"http://127.0.0.1:\" + port + \"/search\");\n                request.setMethod(HttpConstant.Method.POST);\n                request.setRequestBody(HttpRequestBody.form(params, \"utf-8\"));\n                httpUriRequest = httpUriRequestConverter.convert(request, site, null).getHttpUriRequest();\n                assertThat(EntityUtils.toString(HttpClients.custom().build().execute(httpUriRequest).getEntity())).isEqualTo(\"post\");\n                request.setMethod(HttpConstant.Method.PUT);\n                httpUriRequest = httpUriRequestConverter.convert(request, site, null).getHttpUriRequest();\n                assertThat(EntityUtils.toString(HttpClients.custom().build().execute(httpUriRequest).getEntity())).isEqualTo(\"put\");\n            }\n        });\n    }\n\n    @Test\n    public void test_set_request_cookie() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.get(eq(cookie(\"cookie\"), \"cookie-webmagic\")).response(\"ok\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setUrl(\"http://127.0.0.1:13423\");\n                request.addCookie(\"cookie\",\"cookie-webmagic\");\n                Page page = httpClientDownloader.download(request, Site.me().toTask());\n                assertThat(page.getRawText()).isEqualTo(\"ok\");\n            }\n        });\n    }\n\n    @Test\n    public void test_disableCookieManagement() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.get(not(eq(cookie(\"cookie\"), \"cookie-webmagic\"))).response(\"ok\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setUrl(\"http://127.0.0.1:13423\");\n                request.addCookie(\"cookie\",\"cookie-webmagic\");\n                Page page = httpClientDownloader.download(request, Site.me().setDisableCookieManagement(true).toTask());\n                assertThat(page.getRawText()).isEqualTo(\"ok\");\n            }\n        });\n    }\n\n    @Test\n    public void test_set_request_header() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.get(eq(header(\"header\"), \"header-webmagic\")).response(\"ok\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setUrl(\"http://127.0.0.1:13423\");\n                request.addHeader(\"header\",\"header-webmagic\");\n                Page page = httpClientDownloader.download(request, Site.me().toTask());\n                assertThat(page.getRawText()).isEqualTo(\"ok\");\n            }\n        });\n    }\n\n    @Test\n    public void test_set_site_header() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.get(eq(header(\"header\"), \"header-webmagic\")).response(\"ok\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setUrl(\"http://127.0.0.1:13423\");\n                Page page = httpClientDownloader.download(request, Site.me().addHeader(\"header\",\"header-webmagic\").toTask());\n                assertThat(page.getRawText()).isEqualTo(\"ok\");\n            }\n        });\n    }\n\n    @Test\n    public void test_set_site_cookie() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.get(eq(cookie(\"cookie\"), \"cookie-webmagic\")).response(\"ok\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setUrl(\"http://127.0.0.1:13423\");\n                Site site = Site.me().addCookie(\"cookie\", \"cookie-webmagic\").setDomain(\"127.0.0.1\");\n                Page page = httpClientDownloader.download(request, site.toTask());\n                assertThat(page.getRawText()).isEqualTo(\"ok\");\n            }\n        });\n    }\n\n    @Test\n    public void test_download_when_task_is_null() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.response(\"foo\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                final HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setUrl(\"http://127.0.0.1:13423/\");\n                Page page = httpClientDownloader.download(request, Site.me().toTask());\n                assertThat(page.getRawText()).isEqualTo(\"foo\");\n            }\n        });\n    }\n\n    @Test\n    public void test_download_auth_by_SimpleProxyProvider() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.get(eq(header(\"Proxy-Authorization\"), \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\")).response(\"ok\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy(\"127.0.0.1\", 13423, \"username\", \"password\")));\n                Request request = new Request();\n                request.setUrl(\"http://www.baidu.com\");\n                Page page = httpClientDownloader.download(request, Site.me().toTask());\n                assertThat(page.getRawText()).isEqualTo(\"ok\");\n            }\n        });\n    }\n\n    @Test\n    public void test_download_binary_content() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.response(\"binary\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                final HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setBinaryContent(true);\n                request.setUrl(\"http://127.0.0.1:13423/\");\n                Page page = httpClientDownloader.download(request, Site.me().toTask());\n                assertThat(page.getRawText()).isNull();\n                assertThat(page.getBytes()).isEqualTo(\"binary\".getBytes());\n            }\n        });\n    }\n\n    @Test\n    public void test_download_set_charset() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.response(header(\"Content-Type\",\"text/html; charset=utf-8\")).response(\"hello world!\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                final HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setUrl(\"http://127.0.0.1:13423/\");\n                Page page = httpClientDownloader.download(request, Site.me().toTask());\n                assertThat(page.getCharset()).isEqualTo(\"utf-8\");\n            }\n        });\n    }\n\n    @Test\n    public void test_download_set_request_charset() throws Exception {\n        HttpServer server = httpServer(13423);\n        server.response(\"hello world!\");\n        Runner.running(server, new Runnable() {\n            @Override\n            public void run() throws Exception {\n                final HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n                Request request = new Request();\n                request.setCharset(\"utf-8\");\n                request.setUrl(\"http://127.0.0.1:13423/\");\n                Page page = httpClientDownloader.download(request, Site.me().setCharset(\"gbk\").toTask());\n                assertThat(page.getCharset()).isEqualTo(\"utf-8\");\n            }\n        });\n    }\n\n    @Test\n    public void test_no_task_download(){\n        Request request = new Request();\n        request.setUrl(\"http://127.0.0.1:13423/\");\n        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n         assertThrows(NullPointerException.class, () -> httpClientDownloader.download(request,null));       \n    }\n\n\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/downloader/HttpUriRequestConverterTest.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.utils.UrlUtils;\n\nimport java.net.URI;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/7/22\n *         Time: 下午5:29\n */\npublic class HttpUriRequestConverterTest {\n\n    @Test\n    public void test_illegal_uri_correct() throws Exception {\n        HttpUriRequestConverter httpUriRequestConverter = new HttpUriRequestConverter();\n        HttpClientRequestContext requestContext = httpUriRequestConverter.convert(new Request(UrlUtils.fixIllegalCharacterInUrl(\"http://bj.zhongkao.com/beikao/yimo/##\")), Site.me(), null);\n        assertThat(requestContext.getHttpUriRequest().getURI()).isEqualTo(new URI(\"http://bj.zhongkao.com/beikao/yimo/#\"));\n    }\n}"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/downloader/MockGithubDownloader.java",
    "content": "package us.codecraft.webmagic.downloader;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport org.apache.commons.io.IOUtils;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.selector.PlainText;\n\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class MockGithubDownloader implements Downloader {\n\n    @Override\n    public Page download(Request request, Task task) {\n        Page page = new Page();\n        InputStream resourceAsStream = this.getClass().getResourceAsStream(\"/html/mock-github.html\");\n        try {\n            page.setRawText(IOUtils.toString(resourceAsStream, Charset.defaultCharset()));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        page.setRequest(new Request(\"https://github.com/code4craft/webmagic\"));\n        page.setUrl(new PlainText(\"https://github.com/code4craft/webmagic\"));\n        return page;\n    }\n\n    @Override\n    public void setThread(int threadNum) {\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/downloader/SSLCompatibilityTest.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/11/29\n *         Time: 下午1:32\n */\npublic class SSLCompatibilityTest {\n\n    @Test\n    public void test_tls12() throws Exception {\n        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();\n        Task task = Site.me().setCycleRetryTimes(5).toTask();\n        Request request = new Request(\"https://juejin.im/\");\n        Page page = httpClientDownloader.download(request, task);\n        assertThat(page.isDownloadSuccess()).isTrue();\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/example/GithubRepoPageProcessorTest.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.downloader.MockGithubDownloader;\nimport us.codecraft.webmagic.pipeline.Pipeline;\nimport us.codecraft.webmagic.processor.example.GithubRepoPageProcessor;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 16/1/19\n *         Time: 上午7:27\n */\npublic class GithubRepoPageProcessorTest {\n\n    @Test\n    public void test_github() throws Exception {\n        Spider.create(new GithubRepoPageProcessor()).addPipeline(new Pipeline() {\n            @Override\n            public void process(ResultItems resultItems, Task task) {\n                assertThat(((String) resultItems.get(\"name\")).trim()).isEqualTo(\"webmagic\");\n                assertThat(((String) resultItems.get(\"author\")).trim()).isEqualTo(\"code4craft\");\n            }\n        }).setDownloader(new MockGithubDownloader()).test(\"https://github.com/code4craft/webmagic\");\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/pipeline/FilePipelineTest.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\n\nimport java.util.UUID;\n\n/**\n * Created by ywooer on 2014/5/6 0006.\n */\npublic class FilePipelineTest {\n\n    private static ResultItems resultItems;\n    private static Task task;\n\n    @BeforeClass\n    public static void before() {\n        resultItems = new ResultItems();\n        resultItems.put(\"content\", \"webmagic 爬虫工具\");\n        Request request = new Request(\"http://www.baidu.com\");\n        resultItems.setRequest(request);\n\n        task = new Task() {\n            @Override\n            public String getUUID() {\n                return UUID.randomUUID().toString();\n            }\n\n            @Override\n            public Site getSite() {\n                return null;\n            }\n        };\n    }\n    @Test\n    public void testProcess() {\n        FilePipeline filePipeline = new FilePipeline();\n        filePipeline.process(resultItems, task);\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/processor/PageProcessorTest.java",
    "content": "package us.codecraft.webmagic.processor;\n\nimport static org.junit.Assert.assertEquals;\n\nimport org.junit.Test;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\n\npublic class PageProcessorTest {\n\n    @Test\n    public void testGetSite() {\n        Site actualSite = new PageProcessor() {\n\n            @Override\n            public void process(Page page) {\n            }\n\n        }.getSite();\n\n        assertEquals(Site.me(), actualSite);\n\n        actualSite = new PageProcessor() {\n\n            @Override\n            public void process(Page page) {\n            }\n\n            @Override\n\t\t\tpublic Site getSite() {\n                return Site.me().setTimeOut(123);\n            };\n\n        }.getSite();\n\n        assertEquals(Site.me().setTimeOut(123), actualSite);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/proxy/ProxyTest.java",
    "content": "package us.codecraft.webmagic.proxy;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.http.HttpHost;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\n/**\n * @author yxssfxwzy@sina.com May 30, 2014\n *\n */\nclass ProxyTest {\n\n    private static List<String[]> httpProxyList = new ArrayList<String[]>();\n\n    @BeforeAll\n    static void before() {\n        // String[] source = { \"0.0.0.1:0\", \"0.0.0.2:0\", \"0.0.0.3:0\",\n        // \"0.0.0.4:0\" };\n        String[] source = { \"::0.0.0.1:0\", \"::0.0.0.2:0\", \"::0.0.0.3:0\", \"::0.0.0.4:0\" };\n        for (String line : source) {\n            httpProxyList.add(new String[] {line.split(\":\")[0], line.split(\":\")[1], line.split(\":\")[2], line.split(\":\")[3] });\n        }\n    }\n\n    class Fetch extends Thread {\n        HttpHost hp;\n\n        public Fetch(HttpHost hp) {\n            this.hp = hp;\n        }\n\n        @Override\n        public void run() {\n            try {\n                System.out.println(\"fetch web page use proxy: \" + hp.getHostName() + \":\" + hp.getPort());\n                sleep(500);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    @Test\n    void testCreate() {\n        Proxy proxy = Proxy.create(URI.create(\"//127.0.0.1:8080\"));\n        assertNull(proxy.getScheme());\n        assertNull(proxy.getUsername());\n        assertNull(proxy.getPassword());\n        assertEquals(\"127.0.0.1\", proxy.getHost());\n        assertEquals(8080, proxy.getPort());\n\n        proxy = Proxy.create(URI.create(\"http://127.0.0.1:8080\"));\n        assertEquals(\"http\", proxy.getScheme());\n        assertNull(proxy.getUsername());\n        assertNull(proxy.getPassword());\n        assertEquals(\"127.0.0.1\", proxy.getHost());\n        assertEquals(8080, proxy.getPort());\n\n        proxy = Proxy.create(URI.create(\"//username:password@127.0.0.1:8080\"));\n        assertNull(proxy.getScheme());\n        assertEquals(\"username\", proxy.getUsername());\n        assertEquals(\"password\", proxy.getPassword());\n        assertEquals(\"127.0.0.1\", proxy.getHost());\n        assertEquals(8080, proxy.getPort());\n\n        proxy = Proxy.create(URI.create(\"//username@127.0.0.1:8080\"));\n        assertNull(proxy.getScheme());\n        assertEquals(\"username\", proxy.getUsername());\n        assertNull(proxy.getPassword());\n        assertEquals(\"127.0.0.1\", proxy.getHost());\n        assertEquals(8080, proxy.getPort());\n\n        proxy = Proxy.create(URI.create(\"//:password@127.0.0.1:8080\"));\n        assertNull(proxy.getScheme());\n        assertNull(proxy.getUsername());\n        assertEquals(\"password\", proxy.getPassword());\n        assertEquals(\"127.0.0.1\", proxy.getHost());\n        assertEquals(8080, proxy.getPort());\n    }\n\n    @Test\n    void testEqualsHashCode() {\n        var proxy0 = new Proxy(\"::1\", 1080);\n        var proxy1 = new Proxy(\"::1\", 1080);\n        assertEquals(proxy0, proxy1);\n        assertEquals(proxy0.hashCode(), proxy1.hashCode());\n    }\n\n    @Test\n    void testToString() {\n        assertEquals(\"//127.0.0.1:8080\", new Proxy(\"127.0.0.1\", 8080).toString());\n        assertEquals(\"http://127.0.0.1:8080\", new Proxy(\"127.0.0.1\", 8080, \"http\").toString());\n        assertEquals(\"//username:password@127.0.0.1:8080\", new Proxy(\"127.0.0.1\", 8080, \"username\", \"password\").toString());\n        assertEquals(\"//username@127.0.0.1:8080\", new Proxy(\"127.0.0.1\", 8080, \"username\", null).toString());\n        assertEquals(\"//:password@127.0.0.1:8080\", new Proxy(\"127.0.0.1\", 8080, null, \"password\").toString());\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/proxy/SimpleProxyProviderTest.java",
    "content": "package us.codecraft.webmagic.proxy;\n\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/4/16\n *         Time: 上午10:29\n */\npublic class SimpleProxyProviderTest {\n\n    public static final Task TASK = Site.me().toTask();\n\n    @Test\n    public void test_get_proxy() throws Exception {\n        Proxy originProxy1 = new Proxy(\"127.0.0.1\", 1087);\n        Proxy originProxy2 = new Proxy(\"127.0.0.1\", 1088);\n        SimpleProxyProvider proxyProvider = SimpleProxyProvider.from(originProxy1, originProxy2);\n        Request request = Mockito.mock(Request.class);\n        Proxy proxy = proxyProvider.getProxy(request, TASK);\n        assertThat(proxy).isEqualTo(originProxy1);\n        proxy = proxyProvider.getProxy(request, TASK);\n        assertThat(proxy).isEqualTo(originProxy2);\n        proxy = proxyProvider.getProxy(request, TASK);\n        assertThat(proxy).isEqualTo(originProxy1);\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/scheduler/DuplicateRemovedSchedulerTest.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mockito;\nimport org.mockito.runners.MockitoJUnitRunner;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.scheduler.component.DuplicateRemover;\nimport us.codecraft.webmagic.utils.HttpConstant;\n\nimport static org.mockito.Matchers.any;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/3/11\n *         Time: 上午11:26\n */\n@RunWith(MockitoJUnitRunner.class)\npublic class DuplicateRemovedSchedulerTest {\n\n    private DuplicateRemovedScheduler duplicateRemovedScheduler = new DuplicateRemovedScheduler() {\n        @Override\n        public Request poll(Task task) {\n            return null;\n        }\n    };\n\n    @Test\n    public void test_no_duplicate_removed_for_post_request() throws Exception {\n        DuplicateRemover duplicateRemover = Mockito.mock(DuplicateRemover.class);\n        duplicateRemovedScheduler.setDuplicateRemover(duplicateRemover);\n        Request request = new Request(\"https://www.google.com/\");\n        request.setMethod(HttpConstant.Method.POST);\n        duplicateRemovedScheduler.push(request, null);\n        verify(duplicateRemover,times(0)).isDuplicate(any(Request.class),any(Task.class));\n    }\n\n    @Test\n    public void test_duplicate_removed_for_get_request() throws Exception {\n        DuplicateRemover duplicateRemover = Mockito.mock(DuplicateRemover.class);\n        duplicateRemovedScheduler.setDuplicateRemover(duplicateRemover);\n        Request request = new Request(\"https://www.google.com/\");\n        request.setMethod(HttpConstant.Method.GET);\n        duplicateRemovedScheduler.push(request, null);\n        verify(duplicateRemover,times(1)).isDuplicate(any(Request.class),any(Task.class));\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/scheduler/PrioritySchedulerTest.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport junit.framework.Assert;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class PrioritySchedulerTest {\n\n    private PriorityScheduler priorityScheduler = new PriorityScheduler();\n\n    private Task task = new Task() {\n        @Override\n        public String getUUID() {\n            return \"1\";\n        }\n\n        @Override\n        public Site getSite() {\n            return null;\n        }\n    };\n\n    @Test\n    public void testDifferentPriority() {\n        Request request = new Request(\"a\");\n        request.setPriority(100);\n        priorityScheduler.push(request,task);\n\n        request = new Request(\"b\");\n        request.setPriority(900);\n        priorityScheduler.push(request,task);\n\n        request = new Request(\"c\");\n        priorityScheduler.push(request,task);\n\n        request = new Request(\"d\");\n        request.setPriority(-900);\n        priorityScheduler.push(request,task);\n\n        Request poll = priorityScheduler.poll(task);\n        Assert.assertEquals(\"b\",poll.getUrl());\n        poll = priorityScheduler.poll(task);\n        Assert.assertEquals(\"a\",poll.getUrl());\n        poll = priorityScheduler.poll(task);\n        Assert.assertEquals(\"c\",poll.getUrl());\n        poll = priorityScheduler.poll(task);\n        Assert.assertEquals(\"d\",poll.getUrl());\n    }\n\n    @Test\n    public void testNoPriority() {\n        Request request = new Request(\"a\");\n        priorityScheduler.push(request,task);\n\n        request = new Request(\"b\");\n        priorityScheduler.push(request,task);\n\n        request = new Request(\"c\");\n        priorityScheduler.push(request,task);\n\n        Request poll = priorityScheduler.poll(task);\n        Assert.assertEquals(\"a\",poll.getUrl());\n\n        poll = priorityScheduler.poll(task);\n        Assert.assertEquals(\"b\",poll.getUrl());\n\n        poll = priorityScheduler.poll(task);\n        Assert.assertEquals(\"c\",poll.getUrl());\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/AndSelectorTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport static org.junit.Assert.assertEquals;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.junit.Test;\n\npublic class AndSelectorTest {\n\n    @Test\n    public void testSelectList() {\n        String htmlContent = \"<!DOCTYPE html>\\n\" +\n                \"<html lang=\\\"en\\\">\\n\" +\n                \"<head>\\n\" +\n                \"    <meta charset=\\\"UTF-8\\\">\\n\" +\n                \"    <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1.0\\\">\\n\" +\n                \"    <title>HTML with XPath</title>\\n\" +\n                \"</head>\\n\" +\n                \"<body>\\n\" +\n                \"    <div class=\\\"container\\\">\\n\" +\n                \"        <div class=\\\"item1\\\">Item 1</div>\\n\" +\n                \"        <div class=\\\"item2\\\">Item 2</div>\\n\" +\n                \"    </div>\\n\" +\n                \"</body>\\n\" +\n                \"</html>\";\n        List<Selector> selectors = new ArrayList<Selector>();\n        selectors.add(new CssSelector(\"div\"));\n        selectors.add(new XpathSelector(\"//div[@class='item1']\"));\n        AndSelector andSelector = new AndSelector(selectors);\n        List<String> result = andSelector.selectList(htmlContent);\n        assertEquals(\"<div class=\\\"item1\\\">\\n Item 1\\n</div>\", result.get(0));\n    }\n\n    @Test\n    public void testSelectList_NoResults() {\n        String htmlContent = \"<!DOCTYPE html>\\n\" +\n                \"<html lang=\\\"en\\\">\\n\" +\n                \"<head>\\n\" +\n                \"    <meta charset=\\\"UTF-8\\\">\\n\" +\n                \"    <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1.0\\\">\\n\" +\n                \"    <title>HTML with XPath</title>\\n\" +\n                \"</head>\\n\" +\n                \"<body>\\n\" +\n                \"    <div class=\\\"container\\\">\\n\" +\n                \"        <div class=\\\"item1\\\">Item 1</div>\\n\" +\n                \"        <div class=\\\"item2\\\">Item 2</div>\\n\" +\n                \"    </div>\\n\" +\n                \"</body>\\n\" +\n                \"</html>\";\n        List<Selector> selectors = new ArrayList<Selector>();\n        selectors.add(new CssSelector(\"div\"));\n        selectors.add(new XpathSelector(\"//div[@class='item']\"));\n        AndSelector andSelector = new AndSelector(selectors);\n        List<String> result = andSelector.selectList(htmlContent);\n        assertEquals(0, result.size());\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/CssSelectorTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.runners.MockitoJUnitRunner;\n\nimport java.util.List;\nimport static org.junit.Assert.*;\n\npublic class CssSelectorTest {\n\n    @Test\n    public void testSelectElement() {\n        CssSelector cssSelector = new CssSelector(\"div\");\n        String htmlContent = \"<html><head><title>Dummy Page</title></head><body><div id=\\\"dummyDiv\\\">Hello World!</div></body></html>\";\n        Document doc = Jsoup.parse(htmlContent);\n        Element dummyElement = doc.getElementById(\"dummyDiv\");\n        Element resultElement = cssSelector.selectElement(dummyElement);\n        assertNotNull(resultElement);\n    }\n\n    @Test\n    public void testSelectList() {\n        CssSelector cssSelector = new CssSelector(\"div\");\n        String htmlContent = \"<html><head><title>Dummy Page</title></head><body><div id=\\\"dummyDiv\\\">Hello World!</div></body></html>\";\n        Document doc = Jsoup.parse(htmlContent);\n        Element dummyElement = doc.getElementById(\"dummyDiv\");\n        List<String> result = cssSelector.selectList(dummyElement);\n        assertEquals(1, result.size());\n        assertEquals(\"[<div id=\\\"dummyDiv\\\">\\n Hello World!\\n</div>]\", result.toString());\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/ExtractorsTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static us.codecraft.webmagic.selector.Selectors.*;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class ExtractorsTest {\n\n    String html = \"<div><h1>test<a href=\\\"xxx\\\">aabbcc</a></h1></div>\";\n\n    String html2 = \"<title>aabbcc</title>\";\n\n    @Test\n    public void testEach() {\n        assertThat($(\"div h1 a\").select(html)).isEqualTo(\"<a href=\\\"xxx\\\">aabbcc</a>\");\n        assertThat($(\"div h1 a\", \"href\").select(html)).isEqualTo(\"xxx\");\n        assertThat($(\"div h1 a\", \"innerHtml\").select(html)).isEqualTo(\"aabbcc\");\n        assertThat(xpath(\"//a/@href\").select(html)).isEqualTo(\"xxx\");\n        assertThat(regex(\"a href=\\\"(.*)\\\"\").select(html)).isEqualTo(\"xxx\");\n        assertThat(regex(\"(a href)=\\\"(.*)\\\"\", 2).select(html)).isEqualTo(\"xxx\");\n    }\n\n    @Test\n    public void testCombo() {\n        assertThat(and($(\"title\"), regex(\"aa(bb)cc\")).select(html2)).isEqualTo(\"bb\");\n        OrSelector or = or($(\"div h1 a\", \"innerHtml\"), xpath(\"//title\"));\n        assertThat(or.select(html)).isEqualTo(\"aabbcc\");\n        assertThat(or.select(html2)).isEqualTo(\"<title>aabbcc</title>\");\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/JsonPathSelectorTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONObject;\nimport org.junit.Test;\n\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmai.com <br>\n */\npublic class JsonPathSelectorTest {\n\n    private String text = \"{ \\\"store\\\": {\\n\" +\n            \"    \\\"book\\\": [ \\n\" +\n            \"      { \\\"category\\\": \\\"reference\\\",\\n\" +\n            \"        \\\"author\\\": \\\"Nigel Rees\\\",\\n\" +\n            \"        \\\"title\\\": \\\"Sayings of the Century\\\",\\n\" +\n            \"        \\\"price\\\": 8.95\\n\" +\n            \"      },\\n\" +\n            \"      { \\\"category\\\": \\\"fiction\\\",\\n\" +\n            \"        \\\"author\\\": \\\"Evelyn Waugh\\\",\\n\" +\n            \"        \\\"title\\\": \\\"Sword of Honour\\\",\\n\" +\n            \"        \\\"price\\\": 12.99,\\n\" +\n            \"        \\\"isbn\\\": \\\"0-553-21311-3\\\"\\n\" +\n            \"      }\\n\" +\n            \"    ],\\n\" +\n            \"    \\\"bicycle\\\": {\\n\" +\n            \"      \\\"color\\\": \\\"red\\\",\\n\" +\n            \"      \\\"price\\\": 19.95\\n\" +\n            \"    }\\n\" +\n            \"  }\\n\" +\n            \"}\";\n\n    @Test\n    public void testJsonPath() {\n        JsonPathSelector jsonPathSelector = new JsonPathSelector(\"$.store.book[*].author\");\n        String select = jsonPathSelector.select(text);\n        List<String> list = jsonPathSelector.selectList(text);\n        assertThat(select).isEqualTo(\"Nigel Rees\");\n        assertThat(list).contains(\"Nigel Rees\",\"Evelyn Waugh\");\n        jsonPathSelector = new JsonPathSelector(\"$.store.book[?(@.category == 'reference')].title\");\n        list = jsonPathSelector.selectList(text);\n        select = jsonPathSelector.select(text);\n        assertThat(select).isEqualTo(\"Sayings of the Century\");\n        assertThat(list).contains(\"Sayings of the Century\");\n        jsonPathSelector = new JsonPathSelector(\"$.store.book[?(@.category == 'reference')]\");\n        select = jsonPathSelector.select(text);\n        JSONObject object1= JSON.parseObject(select);\n        JSONObject object2=JSON.parseObject(\"{\\\"author\\\":\\\"Nigel Rees\\\",\\\"title\\\":\\\"Sayings of the Century\\\",\\\"category\\\":\\\"reference\\\",\\\"price\\\":8.95}\");\n        assertThat(object1).isEqualTo(object2);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/JsonTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmai.com\n * @since 0.5.0\n */\npublic class JsonTest {\n\n    private String text = \"callback({\\\"name\\\":\\\"json\\\"})\";\n\n    private String textWithBrackerInContent = \"callback({\\\"name\\\":\\\"json)\\\"})\";\n\n    @Test\n    public void testRemovePadding() throws Exception {\n        String name = new Json(text).removePadding(\"callback\").jsonPath(\"$.name\").get();\n        assertThat(name).isEqualTo(\"json\");\n    }\n\n    @Test\n    public void testRemovePaddingForQuotes() throws Exception {\n        String name = new Json(textWithBrackerInContent).removePadding(\"callback\").jsonPath(\"$.name\").get();\n        assertThat(name).isEqualTo(\"json)\");\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/LinksSelectorTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.jsoup.Jsoup;\nimport org.junit.Test;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 17/4/8\n *         Time: 下午9:41\n */\npublic class LinksSelectorTest {\n\n    private String html = \"<div><a href='http://whatever.com/aaa'></a></div><div><a href='http://whatever.com/bbb'></a></div>\";\n\n    @Test\n    public void testLinks() throws Exception {\n        LinksSelector linksSelector = new LinksSelector();\n        List<String> links = linksSelector.selectList(html);\n        System.out.println(links);\n\n        html = \"<div><a href='aaa'></a></div><div><a href='http://whatever.com/bbb'></a></div><div><a href='http://other.com/bbb'></a></div>\";\n        links = linksSelector.selectList(Jsoup.parse(html, \"http://whatever.com/\"));\n        System.out.println(links);\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/OrSelectorTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport static org.junit.Assert.assertEquals;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.junit.Test;\n\npublic class OrSelectorTest {\n    @Test\n    public void testSelectList() {\n        String htmlContent = \"<!DOCTYPE html>\\n\" +\n                \"<html lang=\\\"en\\\">\\n\" +\n                \"<head>\\n\" +\n                \"    <meta charset=\\\"UTF-8\\\">\\n\" +\n                \"    <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1.0\\\">\\n\" +\n                \"    <title>HTML with XPath</title>\\n\" +\n                \"</head>\\n\" +\n                \"<body>\\n\" +\n                \"    <div class=\\\"container\\\">\\n\" +\n                \"        <div class=\\\"item1\\\">Item 1</div>\\n\" +\n                \"        <div class=\\\"item2\\\">Item 2</div>\\n\" +\n                \"    </div>\\n\" +\n                \"</body>\\n\" +\n                \"</html>\";\n        String expectedResult = \"[<head>\\n\" +\n                \" <meta charset=\\\"UTF-8\\\">\\n\" +\n                \" <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1.0\\\">\\n\" +\n                \" <title>HTML with XPath</title>\\n\" +\n                \"</head>, <div class=\\\"item1\\\">\\n\" +\n                \" Item 1\\n\" +\n                \"</div>, <div class=\\\"item2\\\">\\n\" +\n                \" Item 2\\n\" +\n                \"</div>]\";\n        List<Selector> selectors = new ArrayList<Selector>();\n        selectors.add(new CssSelector(\"head\"));\n        selectors.add(new XpathSelector(\"//div[@class='item1']\"));\n        selectors.add(new XpathSelector(\"//div[@class='item2']\"));\n        OrSelector orSelector = new OrSelector(selectors);\n        List<String> result = orSelector.selectList(htmlContent);\n        assertEquals(expectedResult, result.toString());\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/RegexSelectorTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.assertj.core.api.Assertions;\nimport org.junit.Test;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class RegexSelectorTest {\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testRegexWithSingleLeftBracket() {\n        String regex = \"\\\\d+(\";\n        new RegexSelector(regex);\n    }\n\n    @Test\n    public void testRegexWithLeftBracketQuoted() {\n        String regex = \"\\\\(.+\";\n        String source = \"(hello world\";\n        RegexSelector regexSelector = new RegexSelector(regex);\n        String select = regexSelector.select(source);\n        Assertions.assertThat(select).isEqualTo(source);\n    }\n\n    @Test\n    public void testRegexWithZeroWidthAssertions() {\n        String regex = \"^.*(?=\\\\?)(?!\\\\?yy)\";\n        String source = \"hello world?xx?yy\";\n        RegexSelector regexSelector = new RegexSelector(regex);\n        String select = regexSelector.select(source);\n        Assertions.assertThat(select).isEqualTo(\"hello world\");\n\n\n        regex = \"\\\\d{3}(?!\\\\d)\";\n        source = \"123456asdf\";\n        regexSelector = new RegexSelector(regex);\n        select = regexSelector.select(source);\n        Assertions.assertThat(select).isEqualTo(\"456\");\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/selector/SelectorTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.junit.Test;\n\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class SelectorTest {\n\n    private String html = \"<div><a href='http://whatever.com/aaa'></a></div><div><a href='http://whatever.com/bbb'></a></div>\";\n\n    @Test\n    public void testChain() throws Exception {\n        Html selectable = new Html(html);\n        List<String> linksWithoutChain = selectable.links().all();\n        Selectable xpath = selectable.xpath(\"//div\");\n        List<String> linksWithChainFirstCall = xpath.links().all();\n        List<String> linksWithChainSecondCall = xpath.links().all();\n        assertThat(linksWithoutChain).hasSameSizeAs(linksWithChainFirstCall);\n        assertThat(linksWithChainFirstCall).hasSameSizeAs(linksWithChainSecondCall);\n    }\n\n    @Test\n    public void testNodes() throws Exception {\n        Html selectable = new Html(html);\n        List<Selectable> links = selectable.xpath(\"//a\").nodes();\n        assertThat(links.get(0).links().get()).isEqualTo(\"http://whatever.com/aaa\");\n    }\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/utils/CharsetUtilsTest.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport java.io.IOException;\n\nimport org.junit.jupiter.api.Test;\n\nclass CharsetUtilsTest {\n\n    @Test\n    void testDetectCharset() throws IOException {\n        assertNull(CharsetUtils.detectCharset(null, new byte[0]));\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/utils/NumberUtilsTest.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class NumberUtilsTest {\n\n\t@Test\n\tpublic void testCompareLong() {\n\t\tAssert.assertEquals(0, NumberUtils.compareLong(0L, 0L));\n\t\tAssert.assertEquals(1, NumberUtils.compareLong(9L, 0L));\n\t\tAssert.assertEquals(-1, NumberUtils.compareLong(0L, 9L));\n\t\tAssert.assertEquals(-1, NumberUtils.compareLong(-9L, 0L));\n\t\tAssert.assertEquals(1, NumberUtils.compareLong(0L, -9L));\n\t}\n}\n"
  },
  {
    "path": "webmagic-core/src/test/java/us/codecraft/webmagic/utils/UrlUtilsTest.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport static org.junit.Assert.assertNull;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-4-21\n * Time: 下午2:22\n */\npublic class UrlUtilsTest {\n\n    @Test\n    public void testFixRelativeUrl() {\n        String absoluteUrl = UrlUtils.canonicalizeUrl(\"aa\", \"http://www.dianping.com/sh/ss/com\");\n        assertThat(absoluteUrl).isEqualTo(\"http://www.dianping.com/sh/ss/aa\");\n\n        absoluteUrl = UrlUtils.canonicalizeUrl(\"../aa\", \"http://www.dianping.com/sh/ss/com\");\n        assertThat(absoluteUrl).isEqualTo(\"http://www.dianping.com/sh/aa\");\n\n        absoluteUrl = UrlUtils.canonicalizeUrl(\"../mshz\", \"http://www.court.gov.cn/zgcpwsw/zgrmfy/\");\n        assertThat(absoluteUrl).isEqualTo(\"http://www.court.gov.cn/zgcpwsw/mshz\");\n\n        absoluteUrl = UrlUtils.canonicalizeUrl(\"..aa\", \"http://www.dianping.com/sh/ss/com\");\n        assertThat(absoluteUrl).isEqualTo(\"http://www.dianping.com/sh/ss/..aa\");\n\n        absoluteUrl = UrlUtils.canonicalizeUrl(\"../../aa\", \"http://www.dianping.com/sh/ss/com/\");\n        assertThat(absoluteUrl).isEqualTo(\"http://www.dianping.com/sh/aa\");\n\n        absoluteUrl = UrlUtils.canonicalizeUrl(\"../../aa\", \"http://www.dianping.com/sh/ss/com\");\n        assertThat(absoluteUrl).isEqualTo(\"http://www.dianping.com/aa\");\n    }\n\n    @Test\n    public void testGetDomain(){\n        String url = \"http://www.dianping.com/aa/\";\n        Assert.assertEquals(\"www.dianping.com\",UrlUtils.getDomain(url));\n        url = \"www.dianping.com/aa/\";\n        Assert.assertEquals(\"www.dianping.com\",UrlUtils.getDomain(url));\n        url = \"http://www.dianping.com\";\n        Assert.assertEquals(\"www.dianping.com\",UrlUtils.getDomain(url));\n    }\n\n    @Test\n    public void testGetCharset() {\n        assertNull(UrlUtils.getCharset(null));\n    }\n\n}\n"
  },
  {
    "path": "webmagic-core/src/test/resources/html/mock-github.html",
    "content": "\n\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\" class=\" is-u2f-enabled\">\n<head prefix=\"og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#\">\n    <meta charset='utf-8'>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta http-equiv=\"Content-Language\" content=\"en\">\n    <meta name=\"viewport\" content=\"width=1020\">\n\n\n    <title>code4craft/webmagic</title>\n    <link rel=\"search\" type=\"application/opensearchdescription+xml\" href=\"/opensearch.xml\" title=\"GitHub\">\n    <link rel=\"fluid-icon\" href=\"https://github.com/fluidicon.png\" title=\"GitHub\">\n    <link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"/apple-touch-icon-114.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"/apple-touch-icon-114.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"/apple-touch-icon-144.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"/apple-touch-icon-144.png\">\n    <meta property=\"fb:app_id\" content=\"1401488693436528\">\n\n    <meta content=\"@github\" name=\"twitter:site\" /><meta content=\"summary\" name=\"twitter:card\" /><meta content=\"code4craft/webmagic\" name=\"twitter:title\" /><meta content=\"webmagic - A scalable web crawler framework.\" name=\"twitter:description\" /><meta content=\"https://avatars2.githubusercontent.com/u/1351884?v=3&amp;s=400\" name=\"twitter:image:src\" />\n    <meta content=\"GitHub\" property=\"og:site_name\" /><meta content=\"object\" property=\"og:type\" /><meta content=\"https://avatars2.githubusercontent.com/u/1351884?v=3&amp;s=400\" property=\"og:image\" /><meta content=\"code4craft/webmagic\" property=\"og:title\" /><meta content=\"https://github.com/code4craft/webmagic\" property=\"og:url\" /><meta content=\"webmagic - A scalable web crawler framework.\" property=\"og:description\" />\n    <meta name=\"browser-stats-url\" content=\"https://api.github.com/_private/browser/stats\">\n    <meta name=\"browser-errors-url\" content=\"https://api.github.com/_private/browser/errors\">\n    <link rel=\"assets\" href=\"https://assets-cdn.github.com/\">\n    <link rel=\"web-socket\" href=\"wss://live.github.com/_sockets/MTM1MTg4NDo3YWI4NmUwOGM3MzhlMjU5MzVhZGNiNmFmOWUxNjExNTpjNWZlMzRmNzk5NjE4NGMxNDQwZDMzY2Q5ZWE3NGRmMmZkZWMwYTg2NTRkOTA2YTU2Mjk5NDYxYTk1ZjljNDJj--22ca52337ffde7621f032b082bfd863eeade6f9c\">\n    <meta name=\"pjax-timeout\" content=\"1000\">\n    <link rel=\"sudo-modal\" href=\"/sessions/sudo_modal\">\n\n    <meta name=\"msapplication-TileImage\" content=\"/windows-tile.png\">\n    <meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n    <meta name=\"selected-link\" value=\"repo_source\" data-pjax-transient>\n\n    <meta name=\"google-site-verification\" content=\"KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU\">\n    <meta name=\"google-analytics\" content=\"UA-3769691-2\">\n\n    <meta content=\"collector.githubapp.com\" name=\"octolytics-host\" /><meta content=\"github\" name=\"octolytics-app-id\" /><meta content=\"6AB91C29:10EF:6D4972F:569D042D\" name=\"octolytics-dimension-request_id\" /><meta content=\"1351884\" name=\"octolytics-actor-id\" /><meta content=\"code4craft\" name=\"octolytics-actor-login\" /><meta content=\"b87866a7952857ad32eeb0a33a8d3f9743660184e01113bc601ed02f292f8597\" name=\"octolytics-actor-hash\" />\n    <meta content=\"/&lt;user-name&gt;/&lt;repo-name&gt;\" data-pjax-transient=\"true\" name=\"analytics-location\" />\n    <meta content=\"Rails, view, files#disambiguate\" data-pjax-transient=\"true\" name=\"analytics-event\" />\n\n\n    <meta class=\"js-ga-set\" name=\"dimension1\" content=\"Logged In\">\n\n\n\n    <meta name=\"hostname\" content=\"github.com\">\n    <meta name=\"user-login\" content=\"code4craft\">\n\n    <meta name=\"expected-hostname\" content=\"github.com\">\n\n    <link rel=\"mask-icon\" href=\"https://assets-cdn.github.com/pinned-octocat.svg\" color=\"#4078c0\">\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"https://assets-cdn.github.com/favicon.ico\">\n\n    <meta content=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" name=\"form-nonce\" />\n\n    <link crossorigin=\"anonymous\" href=\"https://assets-cdn.github.com/assets/github-1b53a0bcb9add868a6c5ae469ecabb8b236ffa8f2b05360fde027f75eb714f1b.css\" media=\"all\" rel=\"stylesheet\" />\n    <link crossorigin=\"anonymous\" href=\"https://assets-cdn.github.com/assets/github2-70af51f1bed4904749e6ef486ad11871c8ce4361ac82bb5f96a090b7f5346580.css\" media=\"all\" rel=\"stylesheet\" />\n\n\n\n\n    <meta http-equiv=\"x-pjax-version\" content=\"4222bfcb881548243f94e18e8a3bcfd0\">\n\n\n    <meta name=\"description\" content=\"webmagic - A scalable web crawler framework.\">\n    <meta name=\"go-import\" content=\"github.com/code4craft/webmagic git https://github.com/code4craft/webmagic.git\">\n\n    <meta content=\"1351884\" name=\"octolytics-dimension-user_id\" /><meta content=\"code4craft\" name=\"octolytics-dimension-user_login\" /><meta content=\"9623064\" name=\"octolytics-dimension-repository_id\" /><meta content=\"code4craft/webmagic\" name=\"octolytics-dimension-repository_nwo\" /><meta content=\"true\" name=\"octolytics-dimension-repository_public\" /><meta content=\"false\" name=\"octolytics-dimension-repository_is_fork\" /><meta content=\"9623064\" name=\"octolytics-dimension-repository_network_root_id\" /><meta content=\"code4craft/webmagic\" name=\"octolytics-dimension-repository_network_root_nwo\" />\n    <link href=\"https://github.com/code4craft/webmagic/commits/master.atom\" rel=\"alternate\" title=\"Recent Commits to webmagic:master\" type=\"application/atom+xml\">\n\n</head>\n\n\n<body class=\"logged_in   env-production macintosh vis-public\">\n<a href=\"#start-of-content\" tabindex=\"1\" class=\"accessibility-aid js-skip-to-content\">Skip to content</a>\n\n\n\n\n\n\n\n<div class=\"header header-logged-in true\" role=\"banner\">\n    <div class=\"container clearfix\">\n\n        <a class=\"header-logo-invertocat\" href=\"https://github.com/\" data-hotkey=\"g d\" aria-label=\"Homepage\" data-ga-click=\"Header, go to dashboard, icon:logo\">\n            <span aria-hidden=\"true\" class=\"mega-octicon octicon-mark-github\"></span>\n        </a>\n\n\n        <div class=\"site-search repo-scope js-site-search\" role=\"search\">\n            <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/search\" class=\"js-site-search-form\" data-global-search-url=\"/search\" data-repo-search-url=\"/code4craft/webmagic/search\" method=\"get\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /></div>\n            <label class=\"js-chromeless-input-container form-control\">\n                <div class=\"scope-badge\">This repository</div>\n                <input type=\"text\"\n                       class=\"js-site-search-focus js-site-search-field is-clearable chromeless-input\"\n                       data-hotkey=\"s\"\n                       name=\"q\"\n                       placeholder=\"Search\"\n                       aria-label=\"Search this repository\"\n                       data-global-scope-placeholder=\"Search GitHub\"\n                       data-repo-scope-placeholder=\"Search\"\n                       tabindex=\"1\"\n                       autocapitalize=\"off\">\n            </label>\n        </form>\n        </div>\n\n        <ul class=\"header-nav left\" role=\"navigation\">\n            <li class=\"header-nav-item\">\n                <a href=\"/pulls\" class=\"js-selected-navigation-item header-nav-link\" data-ga-click=\"Header, click, Nav menu - item:pulls context:user\" data-hotkey=\"g p\" data-selected-links=\"/pulls /pulls/assigned /pulls/mentioned /pulls\">\n                    Pull requests\n                </a>        </li>\n            <li class=\"header-nav-item\">\n                <a href=\"/issues\" class=\"js-selected-navigation-item header-nav-link\" data-ga-click=\"Header, click, Nav menu - item:issues context:user\" data-hotkey=\"g i\" data-selected-links=\"/issues /issues/assigned /issues/mentioned /issues\">\n                    Issues\n                </a>        </li>\n            <li class=\"header-nav-item\">\n                <a class=\"header-nav-link\" href=\"https://gist.github.com/\" data-ga-click=\"Header, go to gist, text:gist\">Gist</a>\n            </li>\n        </ul>\n\n\n        <ul class=\"header-nav user-nav right\" id=\"user-links\">\n            <li class=\"header-nav-item\">\n      <span class=\"js-socket-channel js-updatable-content\"\n            data-channel=\"notification-changed:code4craft\"\n            data-url=\"/notifications/header\">\n      <a href=\"/notifications\" aria-label=\"You have no unread notifications\" class=\"header-nav-link notification-indicator tooltipped tooltipped-s\" data-ga-click=\"Header, go to notifications, icon:read\" data-hotkey=\"g n\">\n          <span class=\"mail-status all-read\"></span>\n          <span aria-hidden=\"true\" class=\"octicon octicon-bell\"></span>\n      </a>  </span>\n\n            </li>\n\n            <li class=\"header-nav-item dropdown js-menu-container\">\n                <a class=\"header-nav-link tooltipped tooltipped-s js-menu-target\" href=\"/new\"\n                   aria-label=\"Create new…\"\n                   data-ga-click=\"Header, create new, icon:add\">\n                    <span aria-hidden=\"true\" class=\"octicon octicon-plus left\"></span>\n                    <span class=\"dropdown-caret\"></span>\n                </a>\n\n                <div class=\"dropdown-menu-content js-menu-content\">\n                    <ul class=\"dropdown-menu dropdown-menu-sw\">\n\n                        <a class=\"dropdown-item\" href=\"/new\" data-ga-click=\"Header, create new repository\">\n                            New repository\n                        </a>\n\n\n                        <a class=\"dropdown-item\" href=\"/organizations/new\" data-ga-click=\"Header, create new organization\">\n                            New organization\n                        </a>\n\n\n\n                        <div class=\"dropdown-divider\"></div>\n                        <div class=\"dropdown-header\">\n                            <span title=\"code4craft/webmagic\">This repository</span>\n                        </div>\n                        <a class=\"dropdown-item\" href=\"/code4craft/webmagic/issues/new\" data-ga-click=\"Header, create new issue\">\n                            New issue\n                        </a>\n                        <a class=\"dropdown-item\" href=\"/code4craft/webmagic/settings/collaboration\" data-ga-click=\"Header, create new collaborator\">\n                            New collaborator\n                        </a>\n\n                    </ul>\n                </div>\n            </li>\n\n            <li class=\"header-nav-item dropdown js-menu-container\">\n                <a class=\"header-nav-link name tooltipped tooltipped-sw js-menu-target\" href=\"/code4craft\"\n                   aria-label=\"View profile and more\"\n                   data-ga-click=\"Header, show menu, icon:avatar\">\n                    <img alt=\"@code4craft\" class=\"avatar\" height=\"20\" src=\"https://avatars2.githubusercontent.com/u/1351884?v=3&amp;s=40\" width=\"20\" />\n                    <span class=\"dropdown-caret\"></span>\n                </a>\n\n                <div class=\"dropdown-menu-content js-menu-content\">\n                    <div class=\"dropdown-menu  dropdown-menu-sw\">\n                        <div class=\" dropdown-header header-nav-current-user css-truncate\">\n                            Signed in as <strong class=\"css-truncate-target\">code4craft</strong>\n\n                        </div>\n\n\n                        <div class=\"dropdown-divider\"></div>\n\n                        <a class=\"dropdown-item\" href=\"/code4craft\" data-ga-click=\"Header, go to profile, text:your profile\">\n                            Your profile\n                        </a>\n                        <a class=\"dropdown-item\" href=\"/stars\" data-ga-click=\"Header, go to starred repos, text:your stars\">\n                            Your stars\n                        </a>\n                        <a class=\"dropdown-item\" href=\"/explore\" data-ga-click=\"Header, go to explore, text:explore\">\n                            Explore\n                        </a>\n                        <a class=\"dropdown-item\" href=\"/integrations\" data-ga-click=\"Header, go to integrations, text:integrations\">\n                            Integrations\n                        </a>\n                        <a class=\"dropdown-item\" href=\"https://help.github.com\" data-ga-click=\"Header, go to help, text:help\">\n                            Help\n                        </a>\n\n                        <div class=\"dropdown-divider\"></div>\n\n                        <a class=\"dropdown-item\" href=\"/settings/profile\" data-ga-click=\"Header, go to settings, icon:settings\">\n                            Settings\n                        </a>\n\n                        <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/logout\" class=\"logout-form\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"ZINKeCzFexhof31oC9cCA+iEXymQ95S66nGpEO1oOhr5jI03Z1aD4k6dtjVPp11IJlwY9sSGIpgQt/SthVhr5Q==\" /></div>\n                        <button class=\"dropdown-item dropdown-signout\" data-ga-click=\"Header, sign out, icon:logout\">\n                            Sign out\n                        </button>\n                    </form>\n                    </div>\n                </div>\n            </li>\n        </ul>\n\n\n\n    </div>\n</div>\n\n\n\n\n\n\n<div id=\"start-of-content\" class=\"accessibility-aid\"></div>\n\n<div id=\"js-flash-container\">\n</div>\n\n\n<div role=\"main\" class=\"main-content\">\n    <div itemscope itemtype=\"http://schema.org/WebPage\">\n        <div id=\"js-repo-pjax-container\" class=\"context-loader-container js-repo-nav-next\" data-pjax-container>\n\n            <div class=\"pagehead repohead instapaper_ignore readability-menu experiment-repo-nav\">\n                <div class=\"container repohead-details-container\">\n\n\n\n                    <ul class=\"pagehead-actions\">\n\n                        <li>\n                            <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/notifications/subscribe\" class=\"js-social-container\" data-autosubmit=\"true\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" data-remote=\"true\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"A8U/nsuWmrNcDVP1LvjcaT2gKFrPqnmC5eOwH18NcsePFGlsinj0uaf9yaNxnk741gXv+8QIVEYn0veSA3qRUQ==\" /></div>      <input id=\"repository_id\" name=\"repository_id\" type=\"hidden\" value=\"9623064\" />\n\n                            <div class=\"select-menu js-menu-container js-select-menu\">\n                                <a href=\"/code4craft/webmagic/subscription\"\n                                   class=\"btn btn-sm btn-with-count select-menu-button js-menu-target\" role=\"button\" tabindex=\"0\" aria-haspopup=\"true\"\n                                   data-ga-click=\"Repository, click Watch settings, action:files#disambiguate\">\n            <span class=\"js-select-button\">\n              <span aria-hidden=\"true\" class=\"octicon octicon-eye\"></span>\n              Unwatch\n            </span>\n                                </a>\n                                <a class=\"social-count js-social-count\" href=\"/code4craft/webmagic/watchers\">\n                                    367\n                                </a>\n\n                                <div class=\"select-menu-modal-holder\">\n                                    <div class=\"select-menu-modal subscription-menu-modal js-menu-content\" aria-hidden=\"true\">\n                                        <div class=\"select-menu-header\">\n                                            <span aria-label=\"Close\" class=\"octicon octicon-x js-menu-close\" role=\"button\"></span>\n                                            <span class=\"select-menu-title\">Notifications</span>\n                                        </div>\n\n                                        <div class=\"select-menu-list js-navigation-container\" role=\"menu\">\n\n                                            <div class=\"select-menu-item js-navigation-item \" role=\"menuitem\" tabindex=\"0\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                <div class=\"select-menu-item-text\">\n                                                    <input id=\"do_included\" name=\"do\" type=\"radio\" value=\"included\" />\n                                                    <span class=\"select-menu-item-heading\">Not watching</span>\n                                                    <span class=\"description\">Be notified when participating or @mentioned.</span>\n                    <span class=\"js-select-button-text hidden-select-button-text\">\n                      <span aria-hidden=\"true\" class=\"octicon octicon-eye\"></span>\n                      Watch\n                    </span>\n                                                </div>\n                                            </div>\n\n                                            <div class=\"select-menu-item js-navigation-item selected\" role=\"menuitem\" tabindex=\"0\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                <div class=\"select-menu-item-text\">\n                                                    <input checked=\"checked\" id=\"do_subscribed\" name=\"do\" type=\"radio\" value=\"subscribed\" />\n                                                    <span class=\"select-menu-item-heading\">Watching</span>\n                                                    <span class=\"description\">Be notified of all conversations.</span>\n                    <span class=\"js-select-button-text hidden-select-button-text\">\n                      <span aria-hidden=\"true\" class=\"octicon octicon-eye\"></span>\n                      Unwatch\n                    </span>\n                                                </div>\n                                            </div>\n\n                                            <div class=\"select-menu-item js-navigation-item \" role=\"menuitem\" tabindex=\"0\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                <div class=\"select-menu-item-text\">\n                                                    <input id=\"do_ignore\" name=\"do\" type=\"radio\" value=\"ignore\" />\n                                                    <span class=\"select-menu-item-heading\">Ignoring</span>\n                                                    <span class=\"description\">Never be notified.</span>\n                    <span class=\"js-select-button-text hidden-select-button-text\">\n                      <span aria-hidden=\"true\" class=\"octicon octicon-mute\"></span>\n                      Stop ignoring\n                    </span>\n                                                </div>\n                                            </div>\n\n                                        </div>\n\n                                    </div>\n                                </div>\n                            </div>\n                        </form>\n                        </li>\n\n                        <li>\n\n                            <div class=\"js-toggler-container js-social-container starring-container \">\n\n                                <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/unstar\" class=\"js-toggler-form starred js-unstar-button\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" data-remote=\"true\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"mGh0BvguuVTHUZ1Lnf51zYVJ7dGdABVF+Bavja/Jqy7OjG/oveUKfauEqgIowVAM3UFe636pTW6E8jHFtSR0Aw==\" /></div>\n                                <button\n                                        class=\"btn btn-sm btn-with-count js-toggler-target\"\n                                        aria-label=\"Unstar this repository\" title=\"Unstar code4craft/webmagic\"\n                                        data-ga-click=\"Repository, click unstar button, action:files#disambiguate; text:Unstar\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-star\"></span>\n                                    Unstar\n                                </button>\n                                <a class=\"social-count js-social-count\" href=\"/code4craft/webmagic/stargazers\">\n                                    1,743\n                                </a>\n                            </form>\n                                <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/star\" class=\"js-toggler-form unstarred js-star-button\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" data-remote=\"true\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"nQnqpsGUUYVDCSka1tYn2QpcwUBYoqFTCehYIBwHWhcW9+tWTg+gBXa/spd+Hhfe2xNjXBfz7iTXZpHy4+ksEg==\" /></div>\n                                <button\n                                        class=\"btn btn-sm btn-with-count js-toggler-target\"\n                                        aria-label=\"Star this repository\" title=\"Star code4craft/webmagic\"\n                                        data-ga-click=\"Repository, click star button, action:files#disambiguate; text:Star\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-star\"></span>\n                                    Star\n                                </button>\n                                <a class=\"social-count js-social-count\" href=\"/code4craft/webmagic/stargazers\">\n                                    1,743\n                                </a>\n                            </form>  </div>\n\n                        </li>\n\n                        <li>\n                            <a href=\"#fork-destination-box\" class=\"btn btn-sm btn-with-count\"\n                               title=\"Fork your own copy of code4craft/webmagic to your account\"\n                               aria-label=\"Fork your own copy of code4craft/webmagic to your account\"\n                               rel=\"facebox\"\n                               data-ga-click=\"Repository, show fork modal, action:files#disambiguate; text:Fork\">\n                                <span aria-hidden=\"true\" class=\"octicon octicon-repo-forked\"></span>\n                                Fork\n                            </a>\n\n                            <div id=\"fork-destination-box\" style=\"display: none;\">\n                                <h2 class=\"facebox-header\" data-facebox-id=\"facebox-header\">Where should we fork this repository?</h2>\n                                <include-fragment src=\"\"\n                                                  class=\"js-fork-select-fragment fork-select-fragment\"\n                                                  data-url=\"/code4craft/webmagic/fork?fragment=1\">\n                                    <img alt=\"Loading\" height=\"64\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-128.gif\" width=\"64\" />\n                                </include-fragment>\n                            </div>\n\n                            <a href=\"/code4craft/webmagic/network\" class=\"social-count\">\n                                1,128\n                            </a>\n                        </li>\n                    </ul>\n\n                    <h1 itemscope itemtype=\"http://data-vocabulary.org/Breadcrumb\" class=\"entry-title public \">\n                        <span aria-hidden=\"true\" class=\"octicon octicon-repo\"></span>\n                        <span class=\"author\"><a href=\"/code4craft\" class=\"url fn\" itemprop=\"url\" rel=\"author\"><span itemprop=\"title\">code4craft</span></a></span><!--\n--><span class=\"path-divider\">/</span><!--\n--><strong><a href=\"/code4craft/webmagic\" data-pjax=\"#js-repo-pjax-container\">webmagic</a></strong>\n\n  <span class=\"page-context-loader\">\n    <img alt=\"\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n  </span>\n\n                    </h1>\n\n                </div>\n                <div class=\"container\">\n\n                    <nav class=\"reponav js-repo-nav js-sidenav-container-pjax js-octicon-loaders\"\n                         role=\"navigation\"\n                         data-pjax=\"#js-repo-pjax-container\">\n\n                        <a href=\"/code4craft/webmagic\" aria-label=\"Code\" aria-selected=\"true\" class=\"js-selected-navigation-item selected reponav-item\" data-hotkey=\"g c\" data-selected-links=\"repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /code4craft/webmagic\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-code\"></span>\n                            Code\n                        </a>\n                        <a href=\"/code4craft/webmagic/issues\" class=\"js-selected-navigation-item reponav-item\" data-hotkey=\"g i\" data-selected-links=\"repo_issues repo_labels repo_milestones /code4craft/webmagic/issues\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-issue-opened\"></span>\n                            Issues\n                            <span class=\"counter\">67</span>\n                        </a>\n                        <a href=\"/code4craft/webmagic/pulls\" class=\"js-selected-navigation-item reponav-item\" data-hotkey=\"g p\" data-selected-links=\"repo_pulls /code4craft/webmagic/pulls\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-git-pull-request\"></span>\n                            Pull requests\n                            <span class=\"counter\">14</span>\n                        </a>\n                        <a href=\"/code4craft/webmagic/wiki\" class=\"js-selected-navigation-item reponav-item\" data-hotkey=\"g w\" data-selected-links=\"repo_wiki /code4craft/webmagic/wiki\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-book\"></span>\n                            Wiki\n                        </a>\n                        <a href=\"/code4craft/webmagic/pulse\" class=\"js-selected-navigation-item reponav-item\" data-selected-links=\"pulse /code4craft/webmagic/pulse\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-pulse\"></span>\n                            Pulse\n                        </a>\n                        <a href=\"/code4craft/webmagic/graphs\" class=\"js-selected-navigation-item reponav-item\" data-selected-links=\"repo_graphs repo_contributors /code4craft/webmagic/graphs\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-graph\"></span>\n                            Graphs\n                        </a>\n                        <a href=\"/code4craft/webmagic/settings\" class=\"js-selected-navigation-item reponav-item\" data-selected-links=\"repo_settings repo_branch_settings hooks /code4craft/webmagic/settings\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-gear\"></span>\n                            Settings\n                        </a>\n                    </nav>\n\n                </div>\n            </div>\n\n            <div class=\"container new-discussion-timeline experiment-repo-nav\">\n                <div class=\"repository-content\">\n\n\n                    <div class=\"repository-meta js-details-container\">\n  <span class=\"repository-meta-content\">\n        A scalable web crawler framework.\n        <a href=\"http://webmagic.io/\" rel=\"nofollow\">http://webmagic.io/</a>\n  </span>\n\n                        <span class=\"edit-link js-details-target\">— <a href=\"#\" class=\"muted-link\">Edit</a></span>\n                        <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/settings/update_meta\" class=\"edit-repository-meta\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"_method\" type=\"hidden\" value=\"put\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"7xX6fGJkjyARqJhxbtYg5AK+hzEpZLP8qatQsSBLDA39GuvJkVwzO80SeWX37wxYpvr1bIudI8ojlj1p5I1zvw==\" /></div>\n\n                        <div class=\"field\">\n                            <label for=\"repo_description\">Description</label>\n                            <input type=\"text\" id=\"repo_description\" class=\"input-contrast repo-description-field\" name=\"repo_description\" value=\"A scalable web crawler framework.\" placeholder=\"Short description of this repository\">\n                        </div>\n\n                        <div class=\"field\" >\n                            <label for=\"repo_homepage\">Website</label>\n                            <input type=\"url\" id=\"repo_homepage\" class=\"input-contrast repo-website-field\" name=\"repo_homepage\" value=\"http://webmagic.io/\" placeholder=\"Website for this repository (optional)\">\n                        </div>\n\n                        <button class=\"btn\">Save</button>\n                        or <a href=\"#\" class=\"js-details-target\">Cancel</a>\n                    </form></div>\n\n\n                    <div class=\"overall-summary overall-summary-bottomless\">\n\n                        <div class=\"stats-switcher-viewport js-stats-switcher-viewport\">\n                            <div class=\"stats-switcher-wrapper\">\n                                <ul class=\"numbers-summary\">\n                                    <li class=\"commits\">\n                                        <a data-pjax href=\"/code4craft/webmagic/commits/master\">\n                                            <span aria-hidden=\"true\" class=\"octicon octicon-history\"></span>\n            <span class=\"num text-emphasized\">\n              698\n            </span>\n                                            commits\n                                        </a>\n                                    </li>\n                                    <li>\n                                        <a data-pjax href=\"/code4craft/webmagic/branches\">\n                                            <span aria-hidden=\"true\" class=\"octicon octicon-git-branch\"></span>\n          <span class=\"num text-emphasized\">\n            6\n          </span>\n                                            branches\n                                        </a>\n                                    </li>\n\n                                    <li>\n                                        <a data-pjax href=\"/code4craft/webmagic/releases\">\n                                            <span aria-hidden=\"true\" class=\"octicon octicon-tag\"></span>\n          <span class=\"num text-emphasized\">\n            13\n          </span>\n                                            releases\n                                        </a>\n                                    </li>\n\n                                    <li>\n\n                                        <a href=\"/code4craft/webmagic/graphs/contributors\">\n                                            <span aria-hidden=\"true\" class=\"octicon octicon-organization\"></span>\n    <span class=\"num text-emphasized\">\n      23\n    </span>\n                                            contributors\n                                        </a>\n                                    </li>\n                                </ul>\n\n                                <div class=\"repository-lang-stats\">\n                                    <ol class=\"repository-lang-stats-numbers\">\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=java\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#b07219;\"></span>\n                                                <span class=\"lang\">Java</span>\n                                                <span class=\"percent\">72.2%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=css\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#563d7c;\"></span>\n                                                <span class=\"lang\">CSS</span>\n                                                <span class=\"percent\">11.6%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=javascript\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#f1e05a;\"></span>\n                                                <span class=\"lang\">JavaScript</span>\n                                                <span class=\"percent\">8.5%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=freemarker\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#0050b2;\"></span>\n                                                <span class=\"lang\">FreeMarker</span>\n                                                <span class=\"percent\">7.4%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=html\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#e44b23;\"></span>\n                                                <span class=\"lang\">HTML</span>\n                                                <span class=\"percent\">0.2%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=ruby\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#701516;\"></span>\n                                                <span class=\"lang\">Ruby</span>\n                                                <span class=\"percent\">0.1%</span>\n                                            </a>\n                                        </li>\n                                    </ol>\n                                </div>\n                            </div>\n                        </div>\n\n                    </div>\n\n                    <div class=\"repository-lang-stats-graph js-toggle-lang-stats\" title=\"Click for language details\">\n                        <span class=\"language-color\" aria-label=\"Java 72.2%\" style=\"width:72.2%; background-color:#b07219;\" itemprop=\"keywords\">Java</span>\n                        <span class=\"language-color\" aria-label=\"CSS 11.6%\" style=\"width:11.6%; background-color:#563d7c;\" itemprop=\"keywords\">CSS</span>\n                        <span class=\"language-color\" aria-label=\"JavaScript 8.5%\" style=\"width:8.5%; background-color:#f1e05a;\" itemprop=\"keywords\">JavaScript</span>\n                        <span class=\"language-color\" aria-label=\"FreeMarker 7.4%\" style=\"width:7.4%; background-color:#0050b2;\" itemprop=\"keywords\">FreeMarker</span>\n                        <span class=\"language-color\" aria-label=\"HTML 0.2%\" style=\"width:0.2%; background-color:#e44b23;\" itemprop=\"keywords\">HTML</span>\n                        <span class=\"language-color\" aria-label=\"Ruby 0.1%\" style=\"width:0.1%; background-color:#701516;\" itemprop=\"keywords\">Ruby</span>\n                    </div>\n\n                    <include-fragment src=\"/code4craft/webmagic/show_partial?partial=tree%2Frecently_touched_branches_list\"></include-fragment>\n\n                    <div class=\"file-navigation in-mid-page file-navigation-new\">\n                        <div class=\"right\">\n                            <div class=\"btn-group\">\n\n                                <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/new/master\" class=\"button_to js-new-blob-form\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"XOKyr9wZjCR+NGJTatrBJTz6EfVIx0qK42atG8cU8mGVCvihIi+04Zb0Y916iB+cmvs9fIDiC+Gg45gG6Y1inw==\" /></div>\n                                <button class=\"btn btn-sm tooltipped tooltipped-n js-new-blob-submit\" type=\"submit\"\n                                        data-disable-with=\"working…\" aria-label=\"Create a new file here\">\n                                    New file\n                                </button>\n                            </form>\n\n\n                                <a href=\"/code4craft/webmagic/find/master\"\n                                   class=\"btn btn-sm empty-icon right js-show-file-finder\"\n                                   data-pjax\n                                   data-hotkey=\"t\"\n                                   data-ga-click=\"Repository, find file, location:repo overview\">\n                                    Find file\n                                </a>\n                            </div>\n                            <div class=\"file-navigation-options\" data-multiple>\n\n                                <div class=\"file-navigation-option\">\n                                    <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/users/set_protocol\" class=\"js-set-user-protocol-preference\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" data-remote=\"true\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"Sx794jiPAE0pdEIUNJhp4AUyhkPwdamIAAKBQQGDtNe+0e8whjFgMrGl63/fDAEmggpzui33hAJ0GQ0EEYf/Rw==\" /></div>\n                                    <input type=\"hidden\" name=\"protocol_type\" value=\"push\">\n\n                                    <div class=\"select-menu js-menu-container js-select-menu\">\n                                        <div class=\"input-group js-select-button js-zeroclipboard-container\">\n                                            <div class=\"input-group-button\">\n                                                <button type=\"button\" class=\"btn btn-sm select-menu-button js-menu-target\" data-ga-click=\"Repository, clone SSH, location:repo overview\">\n                                                    SSH\n                                                </button>\n                                            </div>\n                                            <input type=\"text\" class=\"input-monospace input-mini js-zeroclipboard-target js-url-field\" value=\"git@github.com:code4craft/webmagic.git\" readonly>\n                                            <div class=\"input-group-button\">\n                                                <button aria-label=\"Copy to clipboard\" class=\"js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s\" data-copied-hint=\"Copied!\" type=\"button\"><span aria-hidden=\"true\" class=\"octicon octicon-clippy\"></span></button>\n                                            </div>\n\n                                        </div>\n\n                                        <div class=\"select-menu-modal-holder\">\n                                            <div class=\"select-menu-modal js-menu-content\" aria-hidden=\"true\">\n                                                <div class=\"select-menu-header\">\n                                                    <span aria-label=\"Close\" class=\"octicon octicon-x js-menu-close\" role=\"button\"></span>\n                                                    <span class=\"select-menu-title\">Choose a clone URL</span>\n                                                </div>\n\n                                                <div class=\"select-menu-list js-navigation-container\" role=\"menu\">\n                                                    <div class=\"select-menu-item js-navigation-item \" role=\"menuitem\" tabindex=\"0\">\n                                                        <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                        <div class=\"select-menu-item-text\">\n                                                            <input type=\"radio\" name=\"protocol_selector\" value=\"http\" >\n                          <span class=\"select-menu-item-heading\">\n                            HTTPS\n                            (recommended)\n                          </span>\n                            <span class=\"description\">\n                              Clone with Git or checkout with SVN using the repository's web address.\n                            </span>\n                          <span class=\"js-select-button-text hidden-select-button-text\">\n                            <div class=\"input-group-button\">\n                                <button type=\"button\" class=\"btn btn-sm select-menu-button js-menu-target\" data-ga-click=\"Repository, clone HTTPS, location:repo overview\">\n                                    HTTPS\n                                </button>\n                            </div>\n<input type=\"text\" class=\"input-monospace input-mini js-zeroclipboard-target js-url-field\" value=\"https://github.com/code4craft/webmagic.git\" readonly>\n<div class=\"input-group-button\">\n    <button aria-label=\"Copy to clipboard\" class=\"js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s\" data-copied-hint=\"Copied!\" type=\"button\"><span aria-hidden=\"true\" class=\"octicon octicon-clippy\"></span></button>\n</div>\n\n                          </span>\n                                                        </div>\n                                                    </div>\n                                                    <div class=\"select-menu-item js-navigation-item selected\" role=\"menuitem\" tabindex=\"0\">\n                                                        <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                        <div class=\"select-menu-item-text\">\n                                                            <input type=\"radio\" name=\"protocol_selector\" value=\"ssh\" checked>\n                          <span class=\"select-menu-item-heading\">\n                            SSH\n\n                          </span>\n                            <span class=\"description\">\n                              Clone with an SSH key and passphrase from your GitHub settings.\n                            </span>\n                          <span class=\"js-select-button-text hidden-select-button-text\">\n                            <div class=\"input-group-button\">\n                                <button type=\"button\" class=\"btn btn-sm select-menu-button js-menu-target\" data-ga-click=\"Repository, clone SSH, location:repo overview\">\n                                    SSH\n                                </button>\n                            </div>\n<input type=\"text\" class=\"input-monospace input-mini js-zeroclipboard-target js-url-field\" value=\"git@github.com:code4craft/webmagic.git\" readonly>\n<div class=\"input-group-button\">\n    <button aria-label=\"Copy to clipboard\" class=\"js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s\" data-copied-hint=\"Copied!\" type=\"button\"><span aria-hidden=\"true\" class=\"octicon octicon-clippy\"></span></button>\n</div>\n\n                          </span>\n                                                        </div>\n                                                    </div>\n                                                </div>\n                                                <div class=\"select-menu-list\" role=\"menu\">\n                                                    <a class=\"select-menu-item select-menu-action\" href=\"https://help.github.com/articles/which-remote-url-should-i-use\" target=\"_blank\">\n                                                        <span aria-hidden=\"true\" class=\"octicon octicon-question select-menu-item-icon\"></span>\n                                                        <div class=\"select-menu-item-text\">\n                                                            Learn more about clone URLs\n                                                        </div>\n                                                    </a>\n                                                </div>\n                                            </div>\n                                        </div>\n                                    </div>\n                                </form>        </div>\n\n                                <div class=\"file-navigation-option\">\n                                    <a href=\"github-mac://openRepo/https://github.com/code4craft/webmagic\" class=\"btn btn-sm tooltipped tooltipped-s tooltipped-multiline\" aria-label=\"Save code4craft/webmagic to your computer and use it in GitHub Desktop.\">\n                                        <span aria-hidden=\"true\" class=\"octicon octicon-desktop-download\"></span>\n                                    </a>\n                                </div>\n\n\n                                <div class=\"file-navigation-option\">\n                                    <a href=\"/code4craft/webmagic/archive/master.zip\"\n                                       class=\"btn btn-sm\"\n                                       rel=\"nofollow\"\n                                       data-ga-click=\"Repository, download zip, location:repo overview\">\n                                        Download ZIP\n                                    </a>\n                                </div>\n                            </div>\n                        </div>\n\n\n                        <div class=\"select-menu js-menu-container js-select-menu left\">\n                            <button class=\"btn btn-sm select-menu-button js-menu-target css-truncate\" data-hotkey=\"w\"\n                                    title=\"master\"\n                                    type=\"button\" aria-label=\"Switch branches or tags\" tabindex=\"0\" aria-haspopup=\"true\">\n                                <i>Branch:</i>\n                                <span class=\"js-select-button css-truncate-target\">master</span>\n                            </button>\n\n                            <div class=\"select-menu-modal-holder js-menu-content js-navigation-container\" data-pjax aria-hidden=\"true\">\n\n                                <div class=\"select-menu-modal\">\n                                    <div class=\"select-menu-header\">\n                                        <span aria-label=\"Close\" class=\"octicon octicon-x js-menu-close\" role=\"button\"></span>\n                                        <span class=\"select-menu-title\">Switch branches/tags</span>\n                                    </div>\n\n                                    <div class=\"select-menu-filters\">\n                                        <div class=\"select-menu-text-filter\">\n                                            <input type=\"text\" aria-label=\"Find or create a branch…\" id=\"context-commitish-filter-field\" class=\"js-filterable-field js-navigation-enable\" placeholder=\"Find or create a branch…\">\n                                        </div>\n                                        <div class=\"select-menu-tabs\">\n                                            <ul>\n                                                <li class=\"select-menu-tab\">\n                                                    <a href=\"#\" data-tab-filter=\"branches\" data-filter-placeholder=\"Find or create a branch…\" class=\"js-select-menu-tab\" role=\"tab\">Branches</a>\n                                                </li>\n                                                <li class=\"select-menu-tab\">\n                                                    <a href=\"#\" data-tab-filter=\"tags\" data-filter-placeholder=\"Find a tag…\" class=\"js-select-menu-tab\" role=\"tab\">Tags</a>\n                                                </li>\n                                            </ul>\n                                        </div>\n                                    </div>\n\n                                    <div class=\"select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket\" data-tab-filter=\"branches\" role=\"menu\">\n\n                                        <div data-filterable-for=\"context-commitish-filter-field\" data-filterable-type=\"substring\">\n\n\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/0.4.x\"\n                                               data-name=\"0.4.x\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"0.4.x\">\n                0.4.x\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/0.6.0\"\n                                               data-name=\"0.6.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"0.6.0\">\n                0.6.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/en-webmagic\"\n                                               data-name=\"en-webmagic\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"en-webmagic\">\n                en-webmagic\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/gh-pages\"\n                                               data-name=\"gh-pages\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"gh-pages\">\n                gh-pages\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open selected\"\n                                               href=\"/code4craft/webmagic/tree/master\"\n                                               data-name=\"master\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"master\">\n                master\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/stable\"\n                                               data-name=\"stable\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"stable\">\n                stable\n              </span>\n                                            </a>\n                                        </div>\n\n                                        <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/branches\" class=\"js-create-branch select-menu-item select-menu-new-item-form js-navigation-item js-new-item-form\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"TFV2kT/IcGmiqdH0NqRYxcNkepWIxxCkgnxla0/LxJMYaWluy1/I4QYo83JwZFB5WnNJPxF7S+BqjspGMqGmwA==\" /></div>\n                                        <span aria-hidden=\"true\" class=\"octicon octicon-git-branch select-menu-item-icon\"></span>\n                                        <div class=\"select-menu-item-text\">\n                                            <span class=\"select-menu-item-heading\">Create branch: <span class=\"js-new-item-name\"></span></span>\n                                            <span class=\"description\">from ‘master’</span>\n                                        </div>\n                                        <input type=\"hidden\" name=\"name\" id=\"name\" class=\"js-new-item-value\">\n                                        <input type=\"hidden\" name=\"branch\" id=\"branch\" value=\"master\">\n                                        <input type=\"hidden\" name=\"path\" id=\"path\" value=\"\">\n                                    </form>\n                                    </div>\n\n                                    <div class=\"select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket\" data-tab-filter=\"tags\">\n                                        <div data-filterable-for=\"context-commitish-filter-field\" data-filterable-type=\"substring\">\n\n\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmaigc-0.4.3\"\n                                               data-name=\"webmaigc-0.4.3\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmaigc-0.4.3\">\n                webmaigc-0.4.3\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-parent-0.3.1\"\n                                               data-name=\"webmagic-parent-0.3.1\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-parent-0.3.1\">\n                webmagic-parent-0.3.1\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-parent-0.2.1\"\n                                               data-name=\"webmagic-parent-0.2.1\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-parent-0.2.1\">\n                webmagic-parent-0.2.1\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.4.2\"\n                                               data-name=\"webmagic-0.4.2\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.4.2\">\n                webmagic-0.4.2\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.4.1\"\n                                               data-name=\"webmagic-0.4.1\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.4.1\">\n                webmagic-0.4.1\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.4.0\"\n                                               data-name=\"webmagic-0.4.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.4.0\">\n                webmagic-0.4.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.3.2\"\n                                               data-name=\"webmagic-0.3.2\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.3.2\">\n                webmagic-0.3.2\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.3.0\"\n                                               data-name=\"webmagic-0.3.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.3.0\">\n                webmagic-0.3.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/version-0.2.0\"\n                                               data-name=\"version-0.2.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"version-0.2.0\">\n                version-0.2.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/version-0.1.0\"\n                                               data-name=\"version-0.1.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"version-0.1.0\">\n                version-0.1.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/WebMagic-0.5.2\"\n                                               data-name=\"WebMagic-0.5.2\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"WebMagic-0.5.2\">\n                WebMagic-0.5.2\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/WebMagic-0.5.1\"\n                                               data-name=\"WebMagic-0.5.1\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"WebMagic-0.5.1\">\n                WebMagic-0.5.1\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/WebMagic-0.5.0\"\n                                               data-name=\"WebMagic-0.5.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"WebMagic-0.5.0\">\n                WebMagic-0.5.0\n              </span>\n                                            </a>\n                                        </div>\n\n                                        <div class=\"select-menu-no-results\">Nothing to show</div>\n                                    </div>\n\n                                </div>\n                            </div>\n                        </div>\n\n\n                        <a href=\"/code4craft/webmagic/pull/new/master\" class=\"btn btn-sm btn-primary\" data-pjax data-ga-click=\"Repository, new pull request, location:repo overview\">\n                            New pull request\n                        </a>\n\n                        <div class=\"breadcrumb\">\n\n                        </div>\n                    </div>\n\n\n\n\n                    <div class=\"commit-tease js-details-container\">\n    <span class=\"right\">\n      Latest commit\n      <a class=\"commit-tease-sha\" href=\"/code4craft/webmagic/commit/800f66c4cc7e1e4b3e485af5236e3c9b8d54f028\" data-pjax>\n          800f66c\n      </a>\n      <time datetime=\"2016-01-18T15:20:08Z\" is=\"relative-time\">Jan 18, 2016</time>\n    </span>\n\n\n    <span class=\"commit-author-section\">\n      <img alt=\"@code4craft\" class=\"avatar\" height=\"20\" src=\"https://avatars2.githubusercontent.com/u/1351884?v=3&amp;s=40\" width=\"20\" />\n      <a href=\"/code4craft\" class=\"user-mention\" rel=\"author\">code4craft</a>\n    </span>\n\n                        <a href=\"/code4craft/webmagic/commit/800f66c4cc7e1e4b3e485af5236e3c9b8d54f028\" class=\"message\" data-pjax=\"true\" title=\"Revert &quot;remove some unkown config&quot;\n\nThis reverts commit 0e245c989605c94b8daa21be8da9ac7002c10568.\">Revert \"remove some unkown config\"</a>\n          <span class=\"hidden-text-expander inline\">\n            <a href=\"#\" class=\"js-details-target\">…</a>\n          </span>\n                        </span>\n\n                        <div class=\"commit-desc\"><pre class=\"text-small\">This reverts commit <a href=\"https://github.com/code4craft/webmagic/commit/0e245c989605c94b8daa21be8da9ac7002c10568\" class=\"commit-link\"><tt>0e245c9</tt></a>.</pre></div>\n                    </div>\n\n\n                    <div class=\"file-wrap \">\n\n                        <a href=\"/code4craft/webmagic/tree/800f66c4cc7e1e4b3e485af5236e3c9b8d54f028\" class=\"hidden js-permalink-shortcut\" data-hotkey=\"y\">Permalink</a>\n\n                        <table class=\"files js-navigation-container js-active-navigation-container\" data-pjax>\n\n\n                            <tbody>\n                            <tr class=\"warning include-fragment-error\">\n                                <td class=\"icon\"><span aria-hidden=\"true\" class=\"octicon octicon-alert\"></span></td>\n                                <td class=\"content\" colspan=\"3\">Failed to load latest commit information.</td>\n                            </tr>\n\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/assets\" class=\"js-directory-link js-navigation-open\" id=\"32bb636196f91ed59d7a49190e26b42c-3bc5c153572a8e40990cf593b34139cba724f15c\" title=\"assets\">assets</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/644e8d1f72c08c83348e5c31a42f0f0dfa32f07d\" class=\"message\" data-pjax=\"true\" title=\"同步官方源码\">同步官方源码</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-04-12T14:32:22Z\" is=\"time-ago\">Apr 12, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/en_docs\" class=\"js-directory-link js-navigation-open\" id=\"025516923597c2d7f987828ad6657c14-d80a6b0dee9c88e6b198bc58b3cb0704b3ce07c4\" title=\"en_docs\">en_docs</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/dbebcbe44f07acb8871a0e3f786dd3d10d938a1c\" class=\"message\" data-pjax=\"true\" title=\"docs\">docs</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-05-02T22:14:31Z\" is=\"time-ago\">May 3, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-avalon\" class=\"js-directory-link js-navigation-open\" id=\"079d784782a58fecda2d64e6fadff4ca-c2dff4951c408dd117233ed6a57daa4b7cda0473\" title=\"webmagic-avalon\">webmagic-avalon</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/7668731f08a3118390e7651002d56b2223d4e656\" class=\"message\" data-pjax=\"true\" title=\"update version to snapshot\">update version to snapshot</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-05-04T23:03:55Z\" is=\"time-ago\">May 5, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-core\" class=\"js-directory-link js-navigation-open\" id=\"39809e13bc65c3873f79570b81852d62-a2cf4af3f59391cccb922597dd0c4819a3426667\" title=\"webmagic-core\">webmagic-core</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/90e14b31b0c229d5664092ea01f739f264e419a8\" class=\"message\" data-pjax=\"true\" title=\"修正FileCacheQueueScheduler导致程序不能正常结束和未关闭流\n\nFileCacheQueueScheduler中开启了一个线程周期运行来保存数据但在爬虫结束后没有关闭导致程序无法结束，以及没有关闭io流。\n\n解决方法：\n让FileCacheQueueScheduler实现Closable接口，在close方法中关闭线程以及流。\n在Spider的close方法中添加对scheduler的关闭操作。\">修正FileCacheQueueScheduler导致程序不能正常结束和未关闭流</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2015-11-12T15:10:20Z\" is=\"time-ago\">Nov 12, 2015</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-extension\" class=\"js-directory-link js-navigation-open\" id=\"dc82c79bcb262e1942088502bb426876-35467ae616c037bd947e6752a20167d5fb74d3b5\" title=\"webmagic-extension\">webmagic-extension</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/cfde3b7657d208a80625b61b430bef11889ecc0e\" class=\"message\" data-pjax=\"true\" title=\"Merge pull request #237 from SpenceZhou/master\n\nUpdate RedisScheduler.java\">Merge pull request</a> <a href=\"https://github.com/code4craft/webmagic/pull/237\" class=\"issue-link js-issue-link\" data-url=\"https://github.com/code4craft/webmagic/issues/237\" data-id=\"119897705\" data-error-text=\"Failed to load issue title\" data-permission-text=\"Issue title is private\">#237</a> <a href=\"/code4craft/webmagic/commit/cfde3b7657d208a80625b61b430bef11889ecc0e\" class=\"message\" data-pjax=\"true\" title=\"Merge pull request #237 from SpenceZhou/master\n\nUpdate RedisScheduler.java\">from SpenceZhou/master</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2015-12-02T14:17:00Z\" is=\"time-ago\">Dec 2, 2015</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-samples\" class=\"js-directory-link js-navigation-open\" id=\"4284b70d4c5e11003fb292b0d0f7539f-264e0e2eafe7960dcd72844100faa1460fad5cfb\" title=\"webmagic-samples\">webmagic-samples</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/84b046e4c962841b725cb1be6165f40c549e2ef8\" class=\"message\" data-pjax=\"true\" title=\"Merge pull request #227 from hsqlu/master\n\nupdate deprecated method\">Merge pull request</a> <a href=\"https://github.com/code4craft/webmagic/pull/227\" class=\"issue-link js-issue-link\" data-url=\"https://github.com/code4craft/webmagic/issues/227\" data-id=\"107109677\" data-error-text=\"Failed to load issue title\" data-permission-text=\"Issue title is private\">#227</a> <a href=\"/code4craft/webmagic/commit/84b046e4c962841b725cb1be6165f40c549e2ef8\" class=\"message\" data-pjax=\"true\" title=\"Merge pull request #227 from hsqlu/master\n\nupdate deprecated method\">from hsqlu/master</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2016-01-16T11:36:52Z\" is=\"time-ago\">Jan 16, 2016</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-saxon\" class=\"js-directory-link js-navigation-open\" id=\"5ee0de5b970664e15f6805d957403c63-8311a46ae76f5669f4be3da0e2a01cce327caf97\" title=\"webmagic-saxon\">webmagic-saxon</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5f8c3fd5c518099b7028369fc35df4c01065f42e\" class=\"message\" data-pjax=\"true\" title=\"update version\">update version</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-06-04T09:33:30Z\" is=\"time-ago\">Jun 4, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-scripts\" class=\"js-directory-link js-navigation-open\" id=\"8ecc7fcb462c06097aa24a7048097d3d-0422570614304398e2739f4d5e13c12ee403add9\" title=\"webmagic-scripts\">webmagic-scripts</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5f8c3fd5c518099b7028369fc35df4c01065f42e\" class=\"message\" data-pjax=\"true\" title=\"update version\">update version</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-06-04T09:33:30Z\" is=\"time-ago\">Jun 4, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-selenium\" class=\"js-directory-link js-navigation-open\" id=\"988c197af393f3198711cebacce7fd65-455315f3cbd4108203da09a88afd566d65d161e1\" title=\"webmagic-selenium\">webmagic-selenium</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5d365f7bf46f854d2e05dc31a066cd6c37994fab\" class=\"message\" data-pjax=\"true\" title=\"update and validate pom.xml\n\nUpdate selenium and GhostDriver (PhantomJSDriver) to latest version.\">update and validate pom.xml</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2015-07-11T14:43:49Z\" is=\"time-ago\">Jul 11, 2015</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/zh_docs\" class=\"js-directory-link js-navigation-open\" id=\"bec3b859688b0bbdb94899b1a5b56441-e305b1e0799520204fb6aca537fa5a922240329a\" title=\"zh_docs\">zh_docs</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/2a15bc028962e650463db331794f2b515a77880a\" class=\"message\" data-pjax=\"true\" title=\"contributor\">contributor</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-06-04T14:27:16Z\" is=\"time-ago\">Jun 4, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/.gitignore\" class=\"js-directory-link js-navigation-open\" id=\"a084b794bc0759e7a6b77810e01874f2-0175dbaadc0ab38c5b79ca4a0944fb63b4f8973c\" title=\".gitignore\">.gitignore</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/07ea04223f419d3eb4f3e68c2b69391c93283454\" class=\"message\" data-pjax=\"true\" title=\"change_gitignore\">change_gitignore</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-05-19T07:56:22Z\" is=\"time-ago\">May 19, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/.travis.yml\" class=\"js-directory-link js-navigation-open\" id=\"354f30a63fb0907d4ad57269548329e3-a9f233f37f99ae2dcd5aa2cfefe18738158dd470\" title=\".travis.yml\">.travis.yml</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/73ae7a1d52253bd097283b62a7152f22ffadb60d\" class=\"message\" data-pjax=\"true\" title=\"remove ci for jdk6\">remove ci for jdk6</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2016-01-18T15:19:39Z\" is=\"time-ago\">Jan 18, 2016</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/README.md\" class=\"js-directory-link js-navigation-open\" id=\"04c6e90faac2675aa89e2176d2eec7d8-98fea5a59788254b208d7f2752baf2d77a029dca\" title=\"README.md\">README.md</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5e8ca02ec670e18f52361296072929fc0a93efc3\" class=\"message\" data-pjax=\"true\" title=\"contributor\">contributor</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-06-04T14:26:56Z\" is=\"time-ago\">Jun 4, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/pom.xml\" class=\"js-directory-link js-navigation-open\" id=\"600376dffeb79835ede4a0b285078036-e7290bc95daf3ae60b8ace743d5c822e99223be5\" title=\"pom.xml\">pom.xml</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/800f66c4cc7e1e4b3e485af5236e3c9b8d54f028\" class=\"message\" data-pjax=\"true\" title=\"Revert &quot;remove some unkown config&quot;\n\nThis reverts commit 0e245c989605c94b8daa21be8da9ac7002c10568.\">Revert \"remove some unkown config\"</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2016-01-18T15:20:08Z\" is=\"time-ago\">Jan 18, 2016</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/release-note.md\" class=\"js-directory-link js-navigation-open\" id=\"d59c2d5d8d04d144da5f1cd251c384ad-f44704efd075006a4fc3935fb6607b158f3815b4\" title=\"release-note.md\">release-note.md</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"https://github.com/code4craft/webmagic/issues/34\" class=\"issue-link js-issue-link\" data-url=\"https://github.com/code4craft/webmagic/issues/34\" data-id=\"22319882\" data-error-text=\"Failed to load issue title\" data-permission-text=\"Issue title is private\">#34</a> <a href=\"/code4craft/webmagic/commit/b838c4e4331326e38e7c30c56d39be9d71fc930a\" class=\"message\" data-pjax=\"true\" title=\"#34 Close reader in FileCacheQueueScheduler\">Close reader in FileCacheQueueScheduler</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2013-11-08T06:59:09Z\" is=\"time-ago\">Nov 8, 2013</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/user-manual.md\" class=\"js-directory-link js-navigation-open\" id=\"a5d0f6c7ea51007118aea16b56f50a6a-17f65291cbb26141ec6f27422918d8da7f6b8755\" title=\"user-manual.md\">user-manual.md</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5f6f48931497d80463dace8a97e66e9a7b10d79e\" class=\"message\" data-pjax=\"true\" title=\"deperate in user manual\">deperate in user manual</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-05-02T22:29:37Z\" is=\"time-ago\">May 3, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/webmagic-avalon.md\" class=\"js-directory-link js-navigation-open\" id=\"5fbef994bb80a792d34444969fa7f80c-bcf39ea065c240dd3bbbbb758ada151d2f1e025c\" title=\"webmagic-avalon.md\">webmagic-avalon.md</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/7c43b5146e6eb8c309c3a6cdfd58bda70ab932ec\" class=\"message\" data-pjax=\"true\" title=\"scripts readme\">scripts readme</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2013-11-28T04:04:05Z\" is=\"time-ago\">Nov 28, 2013</time></span>\n                                </td>\n                            </tr>\n                            </tbody>\n                        </table>\n\n                    </div>\n\n\n\n                    <div id=\"readme\" class=\"boxed-group clearfix announce instapaper_body md\">\n                        <h3>\n                            <span aria-hidden=\"true\" class=\"octicon octicon-book\"></span>\n                            README.md\n                        </h3>\n\n                        <article class=\"markdown-body entry-content\" itemprop=\"mainContentOfPage\"><p><a href=\"https://camo.githubusercontent.com/77fe3da40f9b2c5839df0267890a2457a64003e0/68747470733a2f2f7261772e6769746875622e636f6d2f636f64653463726166742f7765626d616769632f6d61737465722f6173736574732f6c6f676f2e6a7067\" target=\"_blank\"><img src=\"https://camo.githubusercontent.com/77fe3da40f9b2c5839df0267890a2457a64003e0/68747470733a2f2f7261772e6769746875622e636f6d2f636f64653463726166742f7765626d616769632f6d61737465722f6173736574732f6c6f676f2e6a7067\" alt=\"logo\" data-canonical-src=\"https://raw.github.com/code4craft/webmagic/master/assets/logo.jpg\" style=\"max-width:100%;\"></a></p>\n\n                            <p><a href=\"https://github.com/code4craft/webmagic/tree/master/zh_docs\">Readme in Chinese</a></p>\n\n                            <p><a href=\"https://github.com/code4craft/webmagic/blob/master/user-manual.md\">User Manual (Chinese)</a></p>\n\n                            <p><a href=\"https://travis-ci.org/code4craft/webmagic\"><img src=\"https://camo.githubusercontent.com/28f799aaf9175c6e3b3c131896651cf1775b2bc8/68747470733a2f2f7472617669732d63692e6f72672f636f64653463726166742f7765626d616769632e706e673f6272616e63683d6d6173746572\" alt=\"Build Status\" data-canonical-src=\"https://travis-ci.org/code4craft/webmagic.png?branch=master\" style=\"max-width:100%;\"></a></p>\n\n                            <blockquote>\n                                <p>A scalable crawler framework. It covers the whole lifecycle of crawler: downloading, url management, content extraction and persistent. It can simplify the development of a  specific crawler.</p>\n                            </blockquote>\n\n                            <h2><a id=\"user-content-features\" class=\"anchor\" href=\"#features\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Features:</h2>\n\n                            <ul>\n                                <li>Simple core with high flexibility.</li>\n                                <li>Simple API for html extracting.</li>\n                                <li>Annotation with POJO to customize a crawler, no configuration.</li>\n                                <li>Multi-thread and Distribution support.</li>\n                                <li>Easy to be integrated.</li>\n                            </ul>\n\n                            <h2><a id=\"user-content-install\" class=\"anchor\" href=\"#install\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Install:</h2>\n\n                            <p>Add dependencies to your pom.xml:</p>\n\n                            <div class=\"highlight highlight-text-xml\"><pre>&lt;<span class=\"pl-ent\">dependency</span>&gt;\n    &lt;<span class=\"pl-ent\">groupId</span>&gt;us.codecraft&lt;/<span class=\"pl-ent\">groupId</span>&gt;\n    &lt;<span class=\"pl-ent\">artifactId</span>&gt;webmagic-core&lt;/<span class=\"pl-ent\">artifactId</span>&gt;\n    &lt;<span class=\"pl-ent\">version</span>&gt;0.5.2&lt;/<span class=\"pl-ent\">version</span>&gt;\n&lt;/<span class=\"pl-ent\">dependency</span>&gt;\n&lt;<span class=\"pl-ent\">dependency</span>&gt;\n    &lt;<span class=\"pl-ent\">groupId</span>&gt;us.codecraft&lt;/<span class=\"pl-ent\">groupId</span>&gt;\n    &lt;<span class=\"pl-ent\">artifactId</span>&gt;webmagic-extension&lt;/<span class=\"pl-ent\">artifactId</span>&gt;\n    &lt;<span class=\"pl-ent\">version</span>&gt;0.5.2&lt;/<span class=\"pl-ent\">version</span>&gt;\n&lt;/<span class=\"pl-ent\">dependency</span>&gt;</pre></div>\n\n                            <p>WebMagic use slf4j with slf4j-log4j12 implementation. If you customized your slf4j implementation, please exclude slf4j-log4j12.</p>\n\n                            <div class=\"highlight highlight-text-xml\"><pre>&lt;<span class=\"pl-ent\">exclusions</span>&gt;\n    &lt;<span class=\"pl-ent\">exclusion</span>&gt;\n        &lt;<span class=\"pl-ent\">groupId</span>&gt;org.slf4j&lt;/<span class=\"pl-ent\">groupId</span>&gt;\n        &lt;<span class=\"pl-ent\">artifactId</span>&gt;slf4j-log4j12&lt;/<span class=\"pl-ent\">artifactId</span>&gt;\n    &lt;/<span class=\"pl-ent\">exclusion</span>&gt;\n&lt;/<span class=\"pl-ent\">exclusions</span>&gt;</pre></div>\n\n                            <h2><a id=\"user-content-get-started\" class=\"anchor\" href=\"#get-started\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Get Started:</h2>\n\n                            <h3><a id=\"user-content-first-crawler\" class=\"anchor\" href=\"#first-crawler\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>First crawler:</h3>\n\n                            <p>Write a class implements PageProcessor. For example, I wrote a crawler of github repository infomation.</p>\n\n                            <div class=\"highlight highlight-source-java\"><pre><span class=\"pl-k\">public</span> <span class=\"pl-k\">class</span> <span class=\"pl-en\">GithubRepoPageProcessor</span> <span class=\"pl-k\">implements</span> <span class=\"pl-e\">PageProcessor</span> {\n\n    <span class=\"pl-k\">private</span> <span class=\"pl-smi\">Site</span> site <span class=\"pl-k\">=</span> <span class=\"pl-smi\">Site</span><span class=\"pl-k\">.</span>me()<span class=\"pl-k\">.</span>setRetryTimes(<span class=\"pl-c1\">3</span>)<span class=\"pl-k\">.</span>setSleepTime(<span class=\"pl-c1\">1000</span>);\n\n    <span class=\"pl-k\">@Override</span>\n    <span class=\"pl-k\">public</span> <span class=\"pl-k\">void</span> <span class=\"pl-en\">process</span>(<span class=\"pl-smi\">Page</span> <span class=\"pl-v\">page</span>) {\n        page<span class=\"pl-k\">.</span>addTargetRequests(page<span class=\"pl-k\">.</span>getHtml()<span class=\"pl-k\">.</span>links()<span class=\"pl-k\">.</span>regex(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>(https://github<span class=\"pl-cce\">\\\\</span>.com/<span class=\"pl-cce\">\\\\</span>w+/<span class=\"pl-cce\">\\\\</span>w+)<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>all());\n        page<span class=\"pl-k\">.</span>putField(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>author<span class=\"pl-pds\">\"</span></span>, page<span class=\"pl-k\">.</span>getUrl()<span class=\"pl-k\">.</span>regex(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github<span class=\"pl-cce\">\\\\</span>.com/(<span class=\"pl-cce\">\\\\</span>w+)/.*<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>toString());\n        page<span class=\"pl-k\">.</span>putField(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>name<span class=\"pl-pds\">\"</span></span>, page<span class=\"pl-k\">.</span>getHtml()<span class=\"pl-k\">.</span>xpath(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>//h1[@class='entry-title public']/strong/a/text()<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>toString());\n        <span class=\"pl-k\">if</span> (page<span class=\"pl-k\">.</span>getResultItems()<span class=\"pl-k\">.</span>get(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>name<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">==</span><span class=\"pl-c1\">null</span>){\n            <span class=\"pl-c\">//skip this page</span>\n            page<span class=\"pl-k\">.</span>setSkip(<span class=\"pl-c1\">true</span>);\n        }\n        page<span class=\"pl-k\">.</span>putField(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>readme<span class=\"pl-pds\">\"</span></span>, page<span class=\"pl-k\">.</span>getHtml()<span class=\"pl-k\">.</span>xpath(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>//div[@id='readme']/tidyText()<span class=\"pl-pds\">\"</span></span>));\n    }\n\n    <span class=\"pl-k\">@Override</span>\n    <span class=\"pl-k\">public</span> <span class=\"pl-smi\">Site</span> <span class=\"pl-en\">getSite</span>() {\n        <span class=\"pl-k\">return</span> site;\n    }\n\n    <span class=\"pl-k\">public</span> <span class=\"pl-k\">static</span> <span class=\"pl-k\">void</span> <span class=\"pl-en\">main</span>(<span class=\"pl-k\">String</span>[] <span class=\"pl-v\">args</span>) {\n        <span class=\"pl-smi\">Spider</span><span class=\"pl-k\">.</span>create(<span class=\"pl-k\">new</span> <span class=\"pl-smi\">GithubRepoPageProcessor</span>())<span class=\"pl-k\">.</span>addUrl(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github.com/code4craft<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>thread(<span class=\"pl-c1\">5</span>)<span class=\"pl-k\">.</span>run();\n    }\n}</pre></div>\n\n                            <ul>\n                                <li><p><code>page.addTargetRequests(links)</code></p>\n\n                                    <p>Add urls for crawling.</p></li>\n                            </ul>\n\n                            <p>You can also use annotation way:</p>\n\n                            <div class=\"highlight highlight-source-java\"><pre>@TargetUrl(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github.com/<span class=\"pl-cce\">\\\\</span>w+/<span class=\"pl-cce\">\\\\</span>w+<span class=\"pl-pds\">\"</span></span>)\n@HelpUrl(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github.com/<span class=\"pl-cce\">\\\\</span>w+<span class=\"pl-pds\">\"</span></span>)\n<span class=\"pl-k\">public</span> <span class=\"pl-k\">class</span> <span class=\"pl-en\">GithubRepo</span> {\n\n    <span class=\"pl-k\">@ExtractBy</span>(<span class=\"pl-c1\">value</span> <span class=\"pl-k\">=</span> <span class=\"pl-s\"><span class=\"pl-pds\">\"</span>//h1[@class='entry-title public']/strong/a/text()<span class=\"pl-pds\">\"</span></span>, <span class=\"pl-c1\">notNull</span> <span class=\"pl-k\">=</span> <span class=\"pl-c1\">true</span>)\n    <span class=\"pl-k\">private</span> <span class=\"pl-smi\">String</span> name;\n\n    <span class=\"pl-k\">@ExtractByUrl</span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github<span class=\"pl-cce\">\\\\</span>.com/(<span class=\"pl-cce\">\\\\</span>w+)/.*<span class=\"pl-pds\">\"</span></span>)\n    <span class=\"pl-k\">private</span> <span class=\"pl-smi\">String</span> author;\n\n    <span class=\"pl-k\">@ExtractBy</span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>//div[@id='readme']/tidyText()<span class=\"pl-pds\">\"</span></span>)\n    <span class=\"pl-k\">private</span> <span class=\"pl-smi\">String</span> readme;\n\n    <span class=\"pl-k\">public</span> <span class=\"pl-k\">static</span> <span class=\"pl-k\">void</span> <span class=\"pl-en\">main</span>(<span class=\"pl-k\">String</span>[] <span class=\"pl-v\">args</span>) {\n        <span class=\"pl-smi\">OOSpider</span><span class=\"pl-k\">.</span>create(<span class=\"pl-smi\">Site</span><span class=\"pl-k\">.</span>me()<span class=\"pl-k\">.</span>setSleepTime(<span class=\"pl-c1\">1000</span>)\n                , <span class=\"pl-k\">new</span> <span class=\"pl-smi\">ConsolePageModelPipeline</span>(), <span class=\"pl-smi\">GithubRepo</span><span class=\"pl-k\">.</span>class)\n                .addUrl(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github.com/code4craft<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>thread(<span class=\"pl-c1\">5</span>)<span class=\"pl-k\">.</span>run();\n    }\n}</pre></div>\n\n                            <h3><a id=\"user-content-docs-and-samples\" class=\"anchor\" href=\"#docs-and-samples\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Docs and samples:</h3>\n\n                            <p>Documents: <a href=\"http://webmagic.io/docs/\">http://webmagic.io/docs/</a></p>\n\n                            <p>The architecture of webmagic (refered to <a href=\"http://scrapy.org/\">Scrapy</a>)</p>\n\n                            <p><a href=\"https://camo.githubusercontent.com/06cb8227231a6adf6d2a57b14b60a25389a25fe9/687474703a2f2f636f64653463726166742e6769746875622e696f2f696d616765732f706f7374732f7765626d616769632e706e67\" target=\"_blank\"><img src=\"https://camo.githubusercontent.com/06cb8227231a6adf6d2a57b14b60a25389a25fe9/687474703a2f2f636f64653463726166742e6769746875622e696f2f696d616765732f706f7374732f7765626d616769632e706e67\" alt=\"image\" data-canonical-src=\"http://code4craft.github.io/images/posts/webmagic.png\" style=\"max-width:100%;\"></a></p>\n\n                            <p>Javadocs: <a href=\"http://code4craft.github.io/webmagic/docs/en/\">http://code4craft.github.io/webmagic/docs/en/</a></p>\n\n                            <p>There are some samples in <code>webmagic-samples</code> package.</p>\n\n                            <h3><a id=\"user-content-lisence\" class=\"anchor\" href=\"#lisence\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Lisence:</h3>\n\n                            <p>Lisenced under <a href=\"http://opensource.org/licenses/Apache-2.0\">Apache 2.0 lisence</a></p>\n\n                            <h3><a id=\"user-content-contributors\" class=\"anchor\" href=\"#contributors\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Contributors:</h3>\n\n                            <p>Thanks these people for commiting source code, reporting bugs or suggesting for new feature:</p>\n\n                            <ul>\n                                <li><a href=\"https://github.com/ccliangbo\">ccliangbo</a></li>\n                                <li><a href=\"https://github.com/yuany\">yuany</a></li>\n                                <li><a href=\"https://github.com/yxssfxwzy\">yxssfxwzy</a></li>\n                                <li><a href=\"https://github.com/linkerlin\">linkerlin</a></li>\n                                <li><a href=\"https://github.com/d0ngw\">d0ngw</a></li>\n                                <li><a href=\"https://github.com/xuchaoo\">xuchaoo</a></li>\n                                <li><a href=\"https://github.com/supermicah\">supermicah</a></li>\n                                <li><a href=\"https://github.com/SimpleExpress\">SimpleExpress</a></li>\n                                <li><a href=\"https://github.com/aruanruan\">aruanruan</a></li>\n                                <li><a href=\"https://github.com/l1z2g9\">l1z2g9</a></li>\n                                <li><a href=\"https://github.com/zhegexiaohuozi\">zhegexiaohuozi</a></li>\n                                <li><a href=\"https://github.com/ywooer\">ywooer</a></li>\n                                <li><a href=\"https://github.com/yyw258520\">yyw258520</a></li>\n                                <li><a href=\"https://github.com/perfecking\">perfecking</a></li>\n                                <li><a href=\"http://my.oschina.net/lidongyang\">lidongyang</a></li>\n                                <li><a href=\"https://github.com/seveniu\">seveniu</a></li>\n                                <li><a href=\"https://github.com/sebastian1118\">sebastian1118</a></li>\n                                <li><a href=\"https://github.com/codev777\">codev777</a></li>\n                                <li><a href=\"https://github.com/fengwuze\">fengwuze</a></li>\n                            </ul>\n\n                            <h3><a id=\"user-content-thanks\" class=\"anchor\" href=\"#thanks\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Thanks:</h3>\n\n                            <p>To write webmagic, I refered to the projects below :</p>\n\n                            <ul>\n                                <li><p><strong>Scrapy</strong></p>\n\n                                    <p>A crawler framework in Python.</p>\n\n                                    <p><a href=\"http://scrapy.org/\">http://scrapy.org/</a></p></li>\n                                <li><p><strong>Spiderman</strong></p>\n\n                                    <p>Another crawler framework in Java.</p>\n\n                                    <p><a href=\"https://gitcafe.com/laiweiwei/Spiderman\">https://gitcafe.com/laiweiwei/Spiderman</a></p></li>\n                            </ul>\n\n                            <h3><a id=\"user-content-mail-list\" class=\"anchor\" href=\"#mail-list\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Mail-list:</h3>\n\n                            <p><a href=\"https://groups.google.com/forum/#!forum/webmagic-java\">https://groups.google.com/forum/#!forum/webmagic-java</a></p>\n\n                            <p><a href=\"http://list.qq.com/cgi-bin/qf_invite?id=023a01f505246785f77c5a5a9aff4e57ab20fcdde871e988\">http://list.qq.com/cgi-bin/qf_invite?id=023a01f505246785f77c5a5a9aff4e57ab20fcdde871e988</a></p>\n\n                            <p>QQ Group: 373225642</p>\n\n                            <p><a href=\"https://bitdeli.com/free\" title=\"Bitdeli Badge\"><img src=\"https://camo.githubusercontent.com/ac3c3cde05f612ce1a1c9a8be3bf2893ffa6d64d/68747470733a2f2f64327765637a68766c38323376302e636c6f756466726f6e742e6e65742f636f64653463726166742f7765626d616769632f7472656e642e706e67\" alt=\"Bitdeli Badge\" data-canonical-src=\"https://d2weczhvl823v0.cloudfront.net/code4craft/webmagic/trend.png\" style=\"max-width:100%;\"></a></p>\n                        </article>\n                    </div>\n\n\n                </div>\n                <div class=\"modal-backdrop\"></div>\n            </div>\n\n        </div>\n    </div>\n\n</div>\n\n<div class=\"container\">\n    <div class=\"site-footer\" role=\"contentinfo\">\n        <ul class=\"site-footer-links right\">\n            <li><a href=\"https://status.github.com/\" data-ga-click=\"Footer, go to status, text:status\">Status</a></li>\n            <li><a href=\"https://developer.github.com\" data-ga-click=\"Footer, go to api, text:api\">API</a></li>\n            <li><a href=\"https://training.github.com\" data-ga-click=\"Footer, go to training, text:training\">Training</a></li>\n            <li><a href=\"https://shop.github.com\" data-ga-click=\"Footer, go to shop, text:shop\">Shop</a></li>\n            <li><a href=\"https://github.com/blog\" data-ga-click=\"Footer, go to blog, text:blog\">Blog</a></li>\n            <li><a href=\"https://github.com/about\" data-ga-click=\"Footer, go to about, text:about\">About</a></li>\n            <li><a href=\"https://github.com/pricing\" data-ga-click=\"Footer, go to pricing, text:pricing\">Pricing</a></li>\n\n        </ul>\n\n        <a href=\"https://github.com\" aria-label=\"Homepage\">\n            <span aria-hidden=\"true\" class=\"mega-octicon octicon-mark-github\" title=\"GitHub \"></span>\n        </a>\n        <ul class=\"site-footer-links\">\n            <li>&copy; 2016 <span title=\"0.16501s from github-fe119-cp1-prd.iad.github.net\">GitHub</span>, Inc.</li>\n            <li><a href=\"https://github.com/site/terms\" data-ga-click=\"Footer, go to terms, text:terms\">Terms</a></li>\n            <li><a href=\"https://github.com/site/privacy\" data-ga-click=\"Footer, go to privacy, text:privacy\">Privacy</a></li>\n            <li><a href=\"https://github.com/security\" data-ga-click=\"Footer, go to security, text:security\">Security</a></li>\n            <li><a href=\"https://github.com/contact\" data-ga-click=\"Footer, go to contact, text:contact\">Contact</a></li>\n            <li><a href=\"https://help.github.com\" data-ga-click=\"Footer, go to help, text:help\">Help</a></li>\n        </ul>\n    </div>\n</div>\n\n\n\n\n\n\n\n<div id=\"ajax-error-message\" class=\"flash flash-error\">\n    <span aria-hidden=\"true\" class=\"octicon octicon-alert\"></span>\n    <button type=\"button\" class=\"flash-close js-flash-close js-ajax-error-dismiss\" aria-label=\"Dismiss error\">\n        <span aria-hidden=\"true\" class=\"octicon octicon-x\"></span>\n    </button>\n    Something went wrong with that request. Please try again.\n</div>\n\n\n<script crossorigin=\"anonymous\" src=\"https://assets-cdn.github.com/assets/frameworks-2895475c714f13790b63e636b5389a6918a260259c5b22a15acf5ef26bd6ef09.js\"></script>\n<script async=\"async\" crossorigin=\"anonymous\" src=\"https://assets-cdn.github.com/assets/github-c0404608a3bcd1310776df0ab26e107bfd70ff0382408f43ede1a81e730e39cd.js\"></script>\n\n\n\n<div class=\"js-stale-session-flash stale-session-flash flash flash-warn flash-banner hidden\">\n    <span aria-hidden=\"true\" class=\"octicon octicon-alert\"></span>\n    <span class=\"signed-in-tab-flash\">You signed in with another tab or window. <a href=\"\">Reload</a> to refresh your session.</span>\n    <span class=\"signed-out-tab-flash\">You signed out in another tab or window. <a href=\"\">Reload</a> to refresh your session.</span>\n</div>\n<div class=\"facebox\" id=\"facebox\" style=\"display:none;\">\n    <div class=\"facebox-popup\">\n        <div class=\"facebox-content\" role=\"dialog\" aria-labelledby=\"facebox-header\" aria-describedby=\"facebox-description\">\n        </div>\n        <button type=\"button\" class=\"facebox-close js-facebox-close\" aria-label=\"Close modal\">\n            <span aria-hidden=\"true\" class=\"octicon octicon-x\"></span>\n        </button>\n    </div>\n</div>\n\n</body>\n</html>\n\n"
  },
  {
    "path": "webmagic-core/src/test/resources/log4j2-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration>\n    <Appenders>\n        <Console name=\"stdout\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"%d{yy-MM-dd HH:mm:ss,SSS} %-5p %c(%F:%L) ## %m%n\" />\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Logger name=\"org.apache\" level=\"warn\" additivity=\"false\">\n            <AppenderRef ref=\"stdout\" />\n        </Logger>\n        <Root level=\"info\">\n            <AppenderRef ref=\"stdout\" />\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "webmagic-coverage/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n    xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://maven.apache.org/POM/4.0.0\n        http://maven.apache.org/maven-v4_0_0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>us.codecraft</groupId>\n        <artifactId>webmagic</artifactId>\n        <version>1.0.4-SNAPSHOT</version>\n    </parent>\n\n    <artifactId>webmagic-coverage</artifactId>\n    <packaging>pom</packaging>\n    <name>webmagic-coverage</name>\n    <description>Compute aggregated test code coverage</description>\n\n    <properties>\n        <maven.deploy.skip>true</maven.deploy.skip>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-core</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-extension</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-scripts</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-selenium</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-saxon</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-samples</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n\n    <reporting>\n        <plugins>\n            <plugin>\n                <groupId>org.jacoco</groupId>\n                <artifactId>jacoco-maven-plugin</artifactId>\n                <reportSets>\n                    <reportSet>\n                        <reports>\n                            <report>report-aggregate</report>\n                        </reports>\n                    </reportSet>\n                </reportSets>\n            </plugin>\n        </plugins>\n    </reporting>\n\n</project>\n"
  },
  {
    "path": "webmagic-extension/README.md",
    "content": "webmagic-extension\n-------\nwebmagic的扩展模块。包括注解格式定义爬虫、JSON、分布式等支持。"
  },
  {
    "path": "webmagic-extension/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n    xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://maven.apache.org/POM/4.0.0\n        http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>us.codecraft</groupId>\n        <artifactId>webmagic</artifactId>\n        <version>1.0.4-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>webmagic-extension</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <version>1.18.32</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>redis.clients</groupId>\n            <artifactId>jedis</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.assertj</groupId>\n            <artifactId>assertj-core</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-core</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/MultiPageModel.java",
    "content": "package us.codecraft.webmagic;\n\nimport us.codecraft.webmagic.utils.Experimental;\n\nimport java.util.Collection;\n\n/**\n * Extract an object of more than one pages, such as news and articles.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\n@Experimental\npublic interface MultiPageModel {\n\n    /**\n     * Page key is the identifier for the object.\n     *\n     * @return page key\n     */\n    public String getPageKey();\n\n    /**\n     * page is the identifier of a page in pages for one object.\n     *\n     * @return page\n     */\n    public String getPage();\n\n    /**\n     * other pages to be extracted.<br>\n     * It is used to judge whether an object contains more than one page, and whether the pages of the object are all extracted.\n     *\n     * @return other pages\n     */\n    public Collection<String> getOtherPages();\n\n    /**\n     * Combine multiPageModels to a whole object.\n     *\n     * @param multiPageModel multiPageModel\n     * @return multiPageModel combined\n     */\n    public MultiPageModel combine(MultiPageModel multiPageModel);\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/SimpleHttpClient.java",
    "content": "package us.codecraft.webmagic;\n\nimport us.codecraft.webmagic.downloader.HttpClientDownloader;\nimport us.codecraft.webmagic.model.PageMapper;\nimport us.codecraft.webmagic.proxy.ProxyProvider;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/5/27\n * @since 0.7.0\n */\npublic class SimpleHttpClient {\n\n    private final HttpClientDownloader httpClientDownloader;\n\n    private final Site site;\n\n    public SimpleHttpClient() {\n        this(Site.me());\n    }\n\n    public SimpleHttpClient(Site site) {\n        this.site = site;\n        this.httpClientDownloader = new HttpClientDownloader();\n    }\n\n    public void setProxyProvider(ProxyProvider proxyProvider){\n        this.httpClientDownloader.setProxyProvider(proxyProvider);\n    }\n\n    public <T> T get(String url, Class<T> clazz) {\n        return get(new Request(url), clazz);\n    }\n\n    public <T> T get(Request request, Class<T> clazz) {\n        Page page = httpClientDownloader.download(request, site.toTask());\n        if (!page.isDownloadSuccess()) {\n            return null;\n        }\n        return new PageMapper<T>(clazz).get(page);\n    }\n\n    public Page get(String url) {\n        return httpClientDownloader.download(new Request(url), site.toTask());\n    }\n\n    public Page get(Request request) {\n        return httpClientDownloader.download(request, site.toTask());\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/configurable/ConfigurablePageProcessor.java",
    "content": "package us.codecraft.webmagic.configurable;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.utils.Experimental;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\n@Experimental\npublic class ConfigurablePageProcessor implements PageProcessor {\n\n    private Site site;\n\n    private List<ExtractRule> extractRules;\n\n    public ConfigurablePageProcessor(Site site, List<ExtractRule> extractRules) {\n        this.site = site;\n        this.extractRules = extractRules;\n    }\n\n    @Override\n    public void process(Page page) {\n        for (ExtractRule extractRule : extractRules) {\n            if (extractRule.isMulti()) {\n                List<String> results = page.getHtml().selectDocumentForList(extractRule.getSelector());\n                if (extractRule.isNotNull() && results.size() == 0) {\n                    page.setSkip(true);\n                } else {\n                    page.getResultItems().put(extractRule.getFieldName(), results);\n                }\n            } else {\n                String result = page.getHtml().selectDocument(extractRule.getSelector());\n                if (extractRule.isNotNull() && result == null) {\n                    page.setSkip(true);\n                } else {\n                    page.getResultItems().put(extractRule.getFieldName(), result);\n                }\n            }\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/configurable/ExpressionType.java",
    "content": "package us.codecraft.webmagic.configurable;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic enum ExpressionType {\n\n    XPath, Regex, Css, JsonPath;\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/configurable/ExtractRule.java",
    "content": "package us.codecraft.webmagic.configurable;\n\nimport us.codecraft.webmagic.selector.JsonPathSelector;\nimport us.codecraft.webmagic.selector.Selector;\n\nimport static us.codecraft.webmagic.selector.Selectors.*;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class ExtractRule {\n\n    private String fieldName;\n\n    private ExpressionType expressionType;\n\n    private String expressionValue;\n\n    private String[] expressionParams;\n\n    private boolean multi = false;\n\n    private volatile Selector selector;\n\n    private boolean notNull = false;\n\n    public String getFieldName() {\n        return fieldName;\n    }\n\n    public void setFieldName(String fieldName) {\n        this.fieldName = fieldName;\n    }\n\n    public ExpressionType getExpressionType() {\n        return expressionType;\n    }\n\n    public void setExpressionType(ExpressionType expressionType) {\n        this.expressionType = expressionType;\n    }\n\n    public String getExpressionValue() {\n        return expressionValue;\n    }\n\n    public void setExpressionValue(String expressionValue) {\n        this.expressionValue = expressionValue;\n    }\n\n    public String[] getExpressionParams() {\n        return expressionParams;\n    }\n\n    public void setExpressionParams(String[] expressionParams) {\n        this.expressionParams = expressionParams;\n    }\n\n    public boolean isMulti() {\n        return multi;\n    }\n\n    public void setMulti(boolean multi) {\n        this.multi = multi;\n    }\n\n    public Selector getSelector() {\n        if (selector == null) {\n            synchronized (this) {\n                if (selector == null) {\n                    selector = compileSelector();\n                }\n            }\n        }\n        return selector;\n    }\n\n    private Selector compileSelector() {\n        switch (expressionType) {\n            case Css:\n                if (expressionParams.length >= 1) {\n                    return $(expressionValue, expressionParams[0]);\n                } else {\n                    return $(expressionValue);\n                }\n            case XPath:\n                return xpath(expressionValue);\n            case Regex:\n                if (expressionParams.length >= 1) {\n                    return regex(expressionValue, Integer.parseInt(expressionParams[0]));\n                } else {\n                    return regex(expressionValue);\n                }\n            case JsonPath:\n                return new JsonPathSelector(expressionValue);\n            default:\n                return xpath(expressionValue);\n        }\n    }\n\n    public void setSelector(Selector selector) {\n        this.selector = selector;\n    }\n\n    public boolean isNotNull() {\n        return notNull;\n    }\n\n    public void setNotNull(boolean notNull) {\n        this.notNull = notNull;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/downloader/PhantomJSDownloader.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.selector.PlainText;\nimport us.codecraft.webmagic.utils.HttpConstant;\n\nimport java.io.*;\n\n/**\n * this downloader is used to download pages which need to render the javascript\n *\n * @author dolphineor@gmail.com\n * @version 0.5.3\n */\npublic class PhantomJSDownloader extends AbstractDownloader {\n    private static final Logger logger = LoggerFactory.getLogger(PhantomJSDownloader.class);\n    private static String crawlJsPath;\n    private static String phantomJsCommand = \"phantomjs\"; // default\n\n    public PhantomJSDownloader() {\n        this.initPhantomjsCrawlPath();\n    }\n\n    /**\n     * 添加新的构造函数，支持phantomjs自定义命令\n     * <p>\n     * example:\n     * phantomjs.exe 支持windows环境\n     * phantomjs --ignore-ssl-errors=yes 忽略抓取地址是https时的一些错误\n     * /usr/local/bin/phantomjs 命令的绝对路径，避免因系统环境变量引起的IOException\n     *\n     * @param phantomJsCommand phantomJsCommand\n     */\n    public PhantomJSDownloader(String phantomJsCommand) {\n        this.initPhantomjsCrawlPath();\n        PhantomJSDownloader.phantomJsCommand = phantomJsCommand;\n    }\n\n    /**\n     * 新增构造函数，支持crawl.js路径自定义，因为当其他项目依赖此jar包时，runtime.exec()执行phantomjs命令时无使用法jar包中的crawl.js\n     * <pre>\n     * crawl.js start --\n     *\n     *   var system = require('system');\n     *   var url = system.args[1];\n     *\n     *   var page = require('webpage').create();\n     *   page.settings.loadImages = false;\n     *   page.settings.resourceTimeout = 5000;\n     *\n     *   page.open(url, function (status) {\n     *       if (status != 'success') {\n     *           console.log(\"HTTP request failed!\");\n     *       } else {\n     *           console.log(page.content);\n     *       }\n     *\n     *       page.close();\n     *       phantom.exit();\n     *   });\n     *\n     * -- crawl.js end\n     * </pre>\n     * 具体项目时可以将以上js代码复制下来使用\n     * <p>\n     * example:\n     * new PhantomJSDownloader(\"/your/path/phantomjs\", \"/your/path/crawl.js\");\n     *\n     * @param phantomJsCommand phantomJsCommand\n     * @param crawlJsPath      crawlJsPath\n     */\n    public PhantomJSDownloader(String phantomJsCommand, String crawlJsPath) {\n        PhantomJSDownloader.phantomJsCommand = phantomJsCommand;\n        PhantomJSDownloader.crawlJsPath = crawlJsPath;\n    }\n\n    private void initPhantomjsCrawlPath() {\n        PhantomJSDownloader.crawlJsPath = new File(this.getClass().getResource(\"/\").getPath()).getPath()\n                + System.getProperty(\"file.separator\") + \"crawl.js \";\n    }\n\n    @Override\n    public Page download(Request request, Task task) {\n        if (logger.isInfoEnabled()) {\n            logger.info(\"downloading page: \" + request.getUrl());\n        }\n\n        Page page = Page.fail(request);\n        try {\n            String content = getPage(request);\n            if (!content.contains(\"HTTP request failed\")) {\n                page.setDownloadSuccess(true);\n                page.setRawText(content);\n                page.setUrl(new PlainText(request.getUrl()));\n                page.setRequest(request);\n                page.setStatusCode(HttpConstant.StatusCode.CODE_200);\n            }\n            onSuccess(page, task);\n        } catch (Exception e) {\n            onError(page, task, e);\n            logger.warn(\"download page {} error\", request.getUrl(), e);\n        }\n        return page;\n    }\n\n    @Override\n    public void setThread(int threadNum) {\n        // ignore\n    }\n\n    protected String getPage(Request request) throws Exception {\n        String url = request.getUrl();\n        Runtime runtime = Runtime.getRuntime();\n        Process process = runtime.exec(phantomJsCommand + \" \" + crawlJsPath + \" \" + url);\n        InputStream is = process.getInputStream();\n        BufferedReader br = new BufferedReader(new InputStreamReader(is));\n        StringBuilder builder = new StringBuilder();\n        String line;\n        while ((line = br.readLine()) != null) {\n            builder.append(line).append(\"\\n\");\n        }\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/example/AppStore.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.utils.Experimental;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.4.1\n */\n@Experimental\npublic class AppStore {\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$..trackName\")\n    private String trackName;\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$..description\")\n    private String description;\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$..userRatingCount\")\n    private int userRatingCount;\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$..screenshotUrls\")\n    private List<String> screenshotUrls;\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$..supportedDevices\")\n    private List<String> supportedDevices;\n\n    public static void main(String[] args) {\n        AppStore appStore = OOSpider.create(Site.me(), AppStore.class).<AppStore>get(\"http://itunes.apple.com/lookup?id=653350791&country=cn&entity=software\");\n        System.out.println(appStore.trackName);\n        System.out.println(appStore.description);\n        System.out.println(appStore.userRatingCount);\n        System.out.println(appStore.screenshotUrls);\n        System.out.println(appStore.supportedDevices);\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/example/BaiduBaike.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @since 0.4.0\n * @author code4crafter@gmail.com\n */\npublic class BaiduBaike{\n\n    @ExtractBy(\"//h1[@class=title]/div[@class=lemmaTitleH1]/text()\")\n    private String name;\n\n    @ExtractBy(\"//div[@id='lemmaContent-0']//div[@class='para']/allText()\")\n    private String description;\n\n    @Override\n    public String toString() {\n        return \"BaiduBaike{\" +\n                \"name='\" + name + '\\'' +\n                \", description='\" + description + '\\'' +\n                '}';\n    }\n\n    public static void main(String[] args) {\n        OOSpider ooSpider = OOSpider.create(Site.me().setSleepTime(0), BaiduBaike.class);\n        //single download\n        String urlTemplate = \"http://baike.baidu.com/search/word?word=%s&pic=1&sug=1&enc=utf8\";\n        BaiduBaike baike = ooSpider.<BaiduBaike>get(\"http://baike.baidu.com/search/word?word=httpclient&pic=1&sug=1&enc=utf8\");\n        System.out.println(baike);\n\n        //multidownload\n        List<String> list = new ArrayList<String>();\n        list.add(String.format(urlTemplate,\"风力发电\"));\n        list.add(String.format(urlTemplate,\"太阳能\"));\n        list.add(String.format(urlTemplate,\"地热发电\"));\n        list.add(String.format(urlTemplate,\"地热发电\"));\n        List<BaiduBaike> resultItemses = ooSpider.<BaiduBaike>getAll(list);\n        for (BaiduBaike resultItemse : resultItemses) {\n            System.out.println(resultItemse);\n        }\n        ooSpider.close();\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/example/GithubRepo.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.ConsolePageModelPipeline;\nimport us.codecraft.webmagic.model.HasKey;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.ExtractByUrl;\nimport us.codecraft.webmagic.model.annotation.HelpUrl;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.3.2\n */\n@TargetUrl(\"https://github.com/\\\\w+/\\\\w+\")\n@HelpUrl({\"https://github.com/\\\\w+\\\\?tab=repositories\", \"https://github.com/\\\\w+\", \"https://github.com/explore/*\"})\npublic class GithubRepo implements HasKey {\n\n    @ExtractBy(value = \"//h1[@class='public']/strong/a/text()\", notNull = true)\n    private String name;\n\n    @ExtractByUrl(\"https://github\\\\.com/(\\\\w+)/.*\")\n    private String author;\n\n    @ExtractBy(\"//div[@id='readme']/tidyText()\")\n    private String readme;\n\n    @ExtractBy(value = \"//div[@class='repository-lang-stats']//li//span[@class='lang']/text()\", multi = true)\n    private List<String> language;\n\n    @ExtractBy(\"//ul[@class='pagehead-actions']/li[1]//a[@class='social-count js-social-count']/text()\")\n    private int star;\n\n    @ExtractBy(\"//ul[@class='pagehead-actions']/li[2]//a[@class='social-count']/text()\")\n    private int fork;\n\n    @ExtractByUrl\n    private String url;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me().setSleepTime(100)\n                , new ConsolePageModelPipeline(), GithubRepo.class)\n                .addUrl(\"https://github.com/code4craft\").thread(10).run();\n    }\n\n    @Override\n    public String key() {\n        return author + \":\" + name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getReadme() {\n        return readme;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public List<String> getLanguage() {\n        return language;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public int getStar() {\n        return star;\n    }\n\n    public int getFork() {\n        return fork;\n    }\n\n    @Override\n    public String toString() {\n        return \"GithubRepo{\" +\n                \"name='\" + name + '\\'' +\n                \", author='\" + author + '\\'' +\n                \", readme='\" + readme + '\\'' +\n                \", language=\" + language +\n                \", star=\" + star +\n                \", fork=\" + fork +\n                \", url='\" + url + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/example/GithubRepoApi.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.ConsolePageModelPipeline;\nimport us.codecraft.webmagic.model.HasKey;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.ExtractByUrl;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.4.1\n */\npublic class GithubRepoApi implements HasKey {\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$.name\", source = ExtractBy.Source.RawText)\n    private String name;\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$..owner.login\", source = ExtractBy.Source.RawText)\n    private String author;\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$.language\",multi = true, source = ExtractBy.Source.RawText)\n    private List<String> language;\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$.stargazers_count\", source = ExtractBy.Source.RawText)\n    private int star;\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$.forks_count\", source = ExtractBy.Source.RawText)\n    private int fork;\n\n    @ExtractByUrl\n    private String url;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me().setSleepTime(100)\n                , new ConsolePageModelPipeline(), GithubRepoApi.class)\n                .addUrl(\"https://api.github.com/repos/code4craft/webmagic\").run();\n    }\n\n    @Override\n    public String key() {\n        return author + \":\" + name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public List<String> getLanguage() {\n        return language;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public int getStar() {\n        return star;\n    }\n\n    public int getFork() {\n        return fork;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/example/GithubRepoPageMapper.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.model.PageMapper;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.3.2\n */\npublic class GithubRepoPageMapper implements PageProcessor {\n\n    private Site site = Site.me().setRetryTimes(3).setSleepTime(0);\n\n    private PageMapper<GithubRepo> githubRepoPageMapper = new PageMapper<GithubRepo>(GithubRepo.class);\n\n    @Override\n    public void process(Page page) {\n        page.addTargetRequests(page.getHtml().links().regex(\"(https://github\\\\.com/\\\\w+/\\\\w+)\").all());\n        page.addTargetRequests(page.getHtml().links().regex(\"(https://github\\\\.com/\\\\w+)\").all());\n        GithubRepo githubRepo = githubRepoPageMapper.get(page);\n        if (githubRepo == null) {\n            page.setSkip(true);\n        } else {\n            page.putField(\"repo\", githubRepo);\n        }\n\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new GithubRepoPageMapper()).addUrl(\"https://github.com/code4craft\").thread(5).run();\n    }\n}"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/example/MonitorExample.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.monitor.SpiderMonitor;\nimport us.codecraft.webmagic.processor.example.GithubRepoPageProcessor;\nimport us.codecraft.webmagic.processor.example.ZhihuPageProcessor;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic class MonitorExample {\n\n    public static void main(String[] args) throws Exception {\n\n        Spider zhihuSpider = Spider.create(new ZhihuPageProcessor())\n                .addUrl(\"http://my.oschina.net/flashsword/blog\");\n        Spider githubSpider = Spider.create(new GithubRepoPageProcessor())\n                .addUrl(\"https://github.com/code4craft\");\n\n        SpiderMonitor.instance().register(zhihuSpider);\n        SpiderMonitor.instance().register(githubSpider);\n        zhihuSpider.start();\n        githubSpider.start();\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/example/OschinaBlog.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.Formatter;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\nimport us.codecraft.webmagic.pipeline.JsonFilePageModelPipeline;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.3.2\n */\n@TargetUrl(\"http://my.oschina.net/flashsword/blog/\\\\d+\")\npublic class OschinaBlog {\n\n    @ExtractBy(\"//title/text()\")\n    private String title;\n\n    @ExtractBy(value = \"div.BlogContent\", type = ExtractBy.Type.Css)\n    private String content;\n\n    @ExtractBy(value = \"//div[@class='BlogTags']/a/text()\", multi = true)\n    private List<String> tags;\n\n    @ExtractBy(\"//div[@class='BlogStat']/regex('\\\\d+-\\\\d+-\\\\d+\\\\s+\\\\d+:\\\\d+')\")\n    private Date date;\n\n    public static void main(String[] args) {\n        //results will be saved to \"/data/webmagic/\" in json format\n        OOSpider.create(Site.me(), new JsonFilePageModelPipeline(\"/data/webmagic/\"), OschinaBlog.class)\n                .addUrl(\"http://my.oschina.net/flashsword/blog\").run();\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public String getContent() {\n        return content;\n    }\n\n    public List<String> getTags() {\n        return tags;\n    }\n\n    public Date getDate() {\n        return date;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/example/PatternProcessorExample.java",
    "content": "package us.codecraft.webmagic.example;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport us.codecraft.webmagic.*;\nimport us.codecraft.webmagic.handler.CompositePageProcessor;\nimport us.codecraft.webmagic.handler.CompositePipeline;\nimport us.codecraft.webmagic.handler.PatternProcessor;\nimport us.codecraft.webmagic.handler.RequestMatcher;\n\n/**\n * Created with IntelliJ IDEA.\n * User: Sebastian MA\n * Date: April 04, 2014\n * Time: 21:23\n */\npublic class PatternProcessorExample {\n\n    private static Logger log = LoggerFactory.getLogger(PatternProcessorExample.class);\n\n    public static void main(String... args) {\n\n        // define a patternProcessor which handles only \"http://item.jd.com/.*\"\n        PatternProcessor githubRepoProcessor = new PatternProcessor(\"https://github\\\\.com/[\\\\w\\\\-]+/[\\\\w\\\\-]+\") {\n\n            @Override\n            public RequestMatcher.MatchOther processPage(Page page) {\n                page.putField(\"reponame\", page.getHtml().xpath(\"//h1[@class='entry-title public']/strong/a/text()\").toString());\n                return RequestMatcher.MatchOther.YES;\n            }\n\n            @Override\n            public RequestMatcher.MatchOther processResult(ResultItems resultItems, Task task) {\n                log.info(\"Extracting from repo\" + resultItems.getRequest());\n                System.out.println(\"Repo name: \"+resultItems.get(\"reponame\"));\n                return RequestMatcher.MatchOther.YES;\n            }\n        };\n\n        PatternProcessor githubUserProcessor = new PatternProcessor(\"https://github\\\\.com/[\\\\w\\\\-]+\") {\n\n            @Override\n            public RequestMatcher.MatchOther processPage(Page page) {\n                log.info(\"Extracting from \" + page.getUrl());\n                page.addTargetRequests(page.getHtml().links().regex(\"https://github\\\\.com/[\\\\w\\\\-]+/[\\\\w\\\\-]+\").all());\n                page.addTargetRequests(page.getHtml().links().regex(\"https://github\\\\.com/[\\\\w\\\\-]+\").all());\n                page.putField(\"username\", page.getHtml().xpath(\"//span[@class='vcard-fullname']/text()\").toString());\n                return RequestMatcher.MatchOther.YES;\n            }\n\n            @Override\n            public RequestMatcher.MatchOther processResult(ResultItems resultItems, Task task) {\n                System.out.println(\"User name: \"+resultItems.get(\"username\"));\n                return RequestMatcher.MatchOther.YES;\n            }\n        };\n\n        CompositePageProcessor pageProcessor = new CompositePageProcessor(Site.me().setDomain(\"github.com\").setRetryTimes(3));\n        CompositePipeline pipeline = new CompositePipeline();\n\n        pageProcessor.setSubPageProcessors(githubRepoProcessor, githubUserProcessor);\n        pipeline.setSubPipeline(githubRepoProcessor, githubUserProcessor);\n\n        Spider.create(pageProcessor).addUrl(\"https://github.com/code4craft\").thread(5).addPipeline(pipeline).runAsync();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/handler/CompositePageProcessor.java",
    "content": "package us.codecraft.webmagic.handler;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class CompositePageProcessor implements PageProcessor {\n\n    private Site site;\n\n    private List<SubPageProcessor> subPageProcessors = new ArrayList<SubPageProcessor>();\n\n    public CompositePageProcessor(Site site) {\n        this.site = site;\n    }\n\n    @Override\n    public void process(Page page) {\n        for (SubPageProcessor subPageProcessor : subPageProcessors) {\n            if (subPageProcessor.match(page.getRequest())) {\n                SubPageProcessor.MatchOther matchOtherProcessorProcessor = subPageProcessor.processPage(page);\n                if (matchOtherProcessorProcessor == null || matchOtherProcessorProcessor != SubPageProcessor.MatchOther.YES) {\n                    return;\n                }\n            }\n        }\n    }\n\n    public CompositePageProcessor setSite(Site site) {\n        this.site = site;\n        return this;\n    }\n\n    public CompositePageProcessor addSubPageProcessor(SubPageProcessor subPageProcessor) {\n        this.subPageProcessors.add(subPageProcessor);\n        return this;\n    }\n\n    public CompositePageProcessor setSubPageProcessors(SubPageProcessor... subPageProcessors) {\n        this.subPageProcessors = new ArrayList<SubPageProcessor>();\n        for (SubPageProcessor subPageProcessor : subPageProcessors) {\n            this.subPageProcessors.add(subPageProcessor);\n        }\n        return this;\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/handler/CompositePipeline.java",
    "content": "package us.codecraft.webmagic.handler;\n\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.pipeline.Pipeline;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class CompositePipeline implements Pipeline {\n\n    private List<SubPipeline> subPipelines = new ArrayList<SubPipeline>();\n\n    @Override\n    public void process(ResultItems resultItems, Task task) {\n        for (SubPipeline subPipeline : subPipelines) {\n            if (subPipeline.match(resultItems.getRequest())) {\n                RequestMatcher.MatchOther matchOtherProcessorProcessor = subPipeline.processResult(resultItems, task);\n                if (matchOtherProcessorProcessor == null || matchOtherProcessorProcessor != RequestMatcher.MatchOther.YES) {\n                    return;\n                }\n            }\n        }\n    }\n\n    public CompositePipeline addSubPipeline(SubPipeline subPipeline) {\n        this.subPipelines.add(subPipeline);\n        return this;\n    }\n\n    public CompositePipeline setSubPipeline(SubPipeline... subPipelines) {\n        this.subPipelines = new ArrayList<SubPipeline>();\n        for (SubPipeline subPipeline : subPipelines) {\n            this.subPipelines.add(subPipeline);\n        }\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/handler/PatternProcessor.java",
    "content": "package us.codecraft.webmagic.handler;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic abstract class PatternProcessor extends PatternRequestMatcher implements SubPipeline, SubPageProcessor {\n    /**\n     * @param pattern url pattern to handle\n     */\n    public PatternProcessor(String pattern) {\n        super(pattern);\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/handler/PatternRequestMatcher.java",
    "content": "package us.codecraft.webmagic.handler;\n\nimport us.codecraft.webmagic.Request;\n\nimport java.util.regex.Pattern;\n\n/**\n * Created with IntelliJ IDEA.\n * User: Sebastian MA\n * Date: April 03, 2014\n * Time: 10:00\n * <p>\n * A PatternHandler is in charge of both page extraction and data processing by implementing\n * its two abstract methods.\n */\npublic abstract class PatternRequestMatcher implements RequestMatcher {\n\n    /**\n     * match pattern. only matched page should be handled.\n     */\n    protected String pattern;\n\n    private Pattern patternCompiled;\n\n    /**\n     * @param pattern url pattern to handle\n     */\n    public PatternRequestMatcher(String pattern) {\n        this.pattern = pattern;\n        this.patternCompiled = Pattern.compile(pattern);\n    }\n\n    @Override\n    public boolean match(Request request) {\n        return patternCompiled.matcher(request.getUrl()).matches();\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/handler/RequestMatcher.java",
    "content": "package us.codecraft.webmagic.handler;\n\nimport us.codecraft.webmagic.Request;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic interface RequestMatcher {\n\n    /**\n     * Check whether to process the page.<br><br>\n     * Please DO NOT change page status in this method.\n     *\n     * @param page page\n     *\n     * @return whether matches\n     */\n    public boolean match(Request page);\n\n    public enum MatchOther {\n        YES, NO\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/handler/SubPageProcessor.java",
    "content": "package us.codecraft.webmagic.handler;\n\nimport us.codecraft.webmagic.Page;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic interface SubPageProcessor extends RequestMatcher {\n\n\t/**\n\t * process the page, extract urls to fetch, extract the data and store\n\t *\n\t * @param page page\n\t *\n\t * @return whether continue to match\n\t */\n\tpublic MatchOther processPage(Page page);\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/handler/SubPipeline.java",
    "content": "package us.codecraft.webmagic.handler;\n\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic interface SubPipeline extends RequestMatcher {\n\n    /**\n     * process the page, extract urls to fetch, extract the data and store\n     *\n     * @param resultItems resultItems\n     * @param task task\n     * @return whether continue to match\n     */\n    public MatchOther processResult(ResultItems resultItems, Task task);\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/AfterExtractor.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.Page;\n\n/**\n * Interface to be implemented by page models that need to do something after fields are extracted.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic interface AfterExtractor {\n\n    public void afterProcess(Page page);\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/ConsolePageModelPipeline.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.pipeline.PageModelPipeline;\n\n/**\n * Print page model in console.<br>\n * Usually used in test.<br>\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class ConsolePageModelPipeline implements PageModelPipeline {\n    @Override\n    public void process(Object o, Task task) {\n        System.out.println(ToStringBuilder.reflectionToString(o));\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/Extractor.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport us.codecraft.webmagic.model.sources.Source;\nimport us.codecraft.webmagic.selector.Selector;\n\n/**\n * The object contains 'ExtractBy' information.\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class Extractor {\n\n    @Getter @Setter\n    protected Selector selector;\n\n    @Getter\n    protected final Source source;\n\n    protected final boolean notNull;\n\n    protected final boolean multi;\n  \n    public Extractor(Selector selector, Source source, boolean notNull, boolean multi) {\n        this.selector = selector;\n        this.source = source;\n        this.notNull = notNull;\n        this.multi = multi;\n    }\n\n    public boolean isNotNull() {\n        return notNull;\n    }\n\n    public boolean isMulti() {\n        return multi;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/FieldExtractor.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.model.formatter.ObjectFormatter;\nimport us.codecraft.webmagic.model.sources.Source;\nimport us.codecraft.webmagic.selector.Selector;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n/**\n * Wrapper of field and extractor.\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class FieldExtractor extends Extractor {\n\n    @Getter\n    private final Field field;\n\n    @Getter @Setter\n    private Method setterMethod;\n\n    @Getter @Setter\n    private ObjectFormatter objectFormatter;\n\n    public FieldExtractor(Field field, Selector selector, Source source, boolean notNull, boolean multi) {\n        super(selector, source, notNull, multi);\n        this.field = field;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/HasKey.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.utils.Experimental;\n\n/**\n * Interface to be implemented by page mode.<br>\n * Can be used to identify a page model, or be used as name of file storing the object.<br>\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\n@Experimental\npublic interface HasKey {\n\n    /**\n     *\n     *\n     * @return key\n     */\n    public String key();\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/ModelPageProcessor.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.selector.Selector;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * The extension to PageProcessor for page model extractor.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\nclass ModelPageProcessor implements PageProcessor {\n\n    private List<PageModelExtractor> pageModelExtractorList = new ArrayList<PageModelExtractor>();\n\n    private Site site;\n\n    private boolean extractLinks = true;\n\n    public static ModelPageProcessor create(Site site, Class... clazzs) {\n        ModelPageProcessor modelPageProcessor = new ModelPageProcessor(site);\n        for (Class clazz : clazzs) {\n            modelPageProcessor.addPageModel(clazz);\n        }\n        return modelPageProcessor;\n    }\n\n\n    public ModelPageProcessor addPageModel(Class clazz) {\n        PageModelExtractor pageModelExtractor = PageModelExtractor.create(clazz);\n        pageModelExtractorList.add(pageModelExtractor);\n        return this;\n    }\n\n    private ModelPageProcessor(Site site) {\n        this.site = site;\n    }\n\n    @Override\n    public void process(Page page) {\n        for (PageModelExtractor pageModelExtractor : pageModelExtractorList) {\n            if (extractLinks) {\n                extractLinks(page, pageModelExtractor.getHelpUrlRegionSelector(), pageModelExtractor.getHelpUrlPatterns());\n                extractLinks(page, pageModelExtractor.getTargetUrlRegionSelector(), pageModelExtractor.getTargetUrlPatterns());\n            }\n            Object process = pageModelExtractor.process(page);\n            if (process == null || (process instanceof List && ((List) process).size() == 0)) {\n                continue;\n            }\n            postProcessPageModel(pageModelExtractor.getClazz(), process);\n            page.putField(pageModelExtractor.getClazz().getCanonicalName(), process);\n        }\n        if (page.getResultItems().getAll().size() == 0) {\n            page.getResultItems().setSkip(true);\n        }\n    }\n\n    private void extractLinks(Page page, Selector urlRegionSelector, List<Pattern> urlPatterns) {\n        List<String> links;\n        if (urlRegionSelector == null) {\n            links = page.getHtml().links().all();\n        } else {\n            links = page.getHtml().selectList(urlRegionSelector).links().all();\n        }\n        for (String link : links) {\n            for (Pattern targetUrlPattern : urlPatterns) {\n                Matcher matcher = targetUrlPattern.matcher(link);\n                if (matcher.find()) {\n                    page.addTargetRequest(new Request(matcher.group(0)));\n                }\n            }\n        }\n    }\n\n    protected void postProcessPageModel(Class clazz, Object object) {\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public boolean isExtractLinks() {\n        return extractLinks;\n    }\n\n    public void setExtractLinks(boolean extractLinks) {\n        this.extractLinks = extractLinks;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/ModelPipeline.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.pipeline.PageModelPipeline;\nimport us.codecraft.webmagic.pipeline.Pipeline;\n\nimport java.lang.annotation.Annotation;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * The extension to Pipeline for page model extractor.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\nclass ModelPipeline implements Pipeline {\n\n    private Map<Class, PageModelPipeline> pageModelPipelines = new ConcurrentHashMap<Class, PageModelPipeline>();\n\n    public ModelPipeline() {\n    }\n\n    public ModelPipeline put(Class clazz, PageModelPipeline pageModelPipeline) {\n        pageModelPipelines.put(clazz, pageModelPipeline);\n        return this;\n    }\n\n    @Override\n    public void process(ResultItems resultItems, Task task) {\n        for (Map.Entry<Class, PageModelPipeline> classPageModelPipelineEntry : pageModelPipelines.entrySet()) {\n            Object o = resultItems.get(classPageModelPipelineEntry.getKey().getCanonicalName());\n            if (o != null) {\n                Annotation annotation = classPageModelPipelineEntry.getKey().getAnnotation(ExtractBy.class);\n                if (annotation == null || !((ExtractBy) annotation).multi()) {\n                    classPageModelPipelineEntry.getValue().process(o, task);\n                } else {\n                    List<Object> list = (List<Object>) o;\n                    for (Object o1 : list) {\n                        classPageModelPipelineEntry.getValue().process(o1, task);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/OOSpider.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.pipeline.CollectorPipeline;\nimport us.codecraft.webmagic.pipeline.PageModelPipeline;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * The spider for page model extractor.<br>\n * In webmagic, we call a POJO containing extract result as \"page model\". <br>\n * You can customize a crawler by write a page model with annotations. <br>\n * Such as:\n * <pre>\n * {@literal @}TargetUrl(\"http://my.oschina.net/flashsword/blog/\\\\d+\")\n *  public class OschinaBlog{\n *\n *      {@literal @}ExtractBy(\"//title\")\n *      private String title;\n *\n *      {@literal @}ExtractBy(value = \"div.BlogContent\",type = ExtractBy.Type.Css)\n *      private String content;\n *\n *      {@literal @}ExtractBy(value = \"//div[@class='BlogTags']/a/text()\", multi = true)\n *      private List&lt;String&gt; tags;\n * }\n * </pre>\n * And start the spider by:\n * <pre>\n *   OOSpider.create(Site.me().addStartUrl(\"http://my.oschina.net/flashsword/blog\")\n *        ,new JsonFilePageModelPipeline(), OschinaBlog.class).run();\n * }\n * </pre>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class OOSpider<T> extends Spider {\n\n    private ModelPageProcessor modelPageProcessor;\n\n    private ModelPipeline modelPipeline;\n\n    private PageModelPipeline pageModelPipeline;\n\n    private List<Class> pageModelClasses = new ArrayList<Class>();\n\n    protected OOSpider(ModelPageProcessor modelPageProcessor) {\n        super(modelPageProcessor);\n        this.modelPageProcessor = modelPageProcessor;\n    }\n\n    public OOSpider(PageProcessor pageProcessor) {\n        super(pageProcessor);\n    }\n\n    /**\n     * create a spider\n     *\n     * @param site site\n     * @param pageModelPipeline pageModelPipeline\n     * @param pageModels pageModels\n     */\n    public OOSpider(Site site, PageModelPipeline pageModelPipeline, Class... pageModels) {\n        this(ModelPageProcessor.create(site, pageModels));\n        this.modelPipeline = new ModelPipeline();\n        super.addPipeline(modelPipeline);\n        for (Class pageModel : pageModels) {\n            if (pageModelPipeline != null) {\n                this.modelPipeline.put(pageModel, pageModelPipeline);\n            }\n            pageModelClasses.add(pageModel);\n        }\n    }\n\n    @Override\n    protected CollectorPipeline getCollectorPipeline() {\n        return new PageModelCollectorPipeline<T>(pageModelClasses.get(0));\n    }\n\n    public static OOSpider create(Site site, Class... pageModels) {\n        return new OOSpider(site, null, pageModels);\n    }\n\n    public static OOSpider create(Site site, PageModelPipeline pageModelPipeline, Class... pageModels) {\n        return new OOSpider(site, pageModelPipeline, pageModels);\n    }\n\n    public OOSpider addPageModel(PageModelPipeline pageModelPipeline, Class... pageModels) {\n        for (Class pageModel : pageModels) {\n            modelPageProcessor.addPageModel(pageModel);\n            modelPipeline.put(pageModel, pageModelPipeline);\n        }\n        return this;\n    }\n\n    public OOSpider setIsExtractLinks(boolean isExtractLinks){\n        modelPageProcessor.setExtractLinks(isExtractLinks);\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/PageMapper.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.Page;\n\nimport java.util.List;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.2\n */\npublic class PageMapper<T> {\n\n    private Class<T> clazz;\n\n    private PageModelExtractor pageModelExtractor;\n\n    public PageMapper(Class<T> clazz) {\n        this.clazz = clazz;\n        this.pageModelExtractor = PageModelExtractor.create(clazz);\n    }\n\n    public T get(Page page) {\n        return (T) pageModelExtractor.process(page);\n    }\n\n    public List<T> getAll(Page page) {\n        return (List<T>) pageModelExtractor.process(page);\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/PageModelCollectorPipeline.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.pipeline.CollectorPageModelPipeline;\nimport us.codecraft.webmagic.pipeline.CollectorPipeline;\n\nimport java.lang.annotation.Annotation;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.4.0\n */\nclass PageModelCollectorPipeline<T> implements CollectorPipeline<T> {\n\n    private final CollectorPageModelPipeline<T> classPipeline = new CollectorPageModelPipeline<T>();\n\n    private final Class<?> clazz;\n\n    PageModelCollectorPipeline(Class<?> clazz) {\n        this.clazz = clazz;\n    }\n\n    @Override\n    public List<T> getCollected() {\n        return classPipeline.getCollected();\n    }\n\n    @Override\n    public synchronized void process(ResultItems resultItems, Task task) {\n        Object o = resultItems.get(clazz.getCanonicalName());\n        if (o != null) {\n            Annotation annotation = clazz.getAnnotation(ExtractBy.class);\n            if (annotation == null || !((ExtractBy) annotation).multi()) {\n                classPipeline.process((T) o, task);\n            } else {\n                List<Object> list = (List<Object>) o;\n                for (Object o1 : list) {\n                   classPipeline.process((T) o1, task);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/PageModelExtractor.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport lombok.Getter;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.model.annotation.*;\nimport us.codecraft.webmagic.model.fields.PageField;\nimport us.codecraft.webmagic.model.formatter.ObjectFormatterBuilder;\nimport us.codecraft.webmagic.model.sources.Source;\nimport us.codecraft.webmagic.model.sources.SourceTextExtractor;\nimport us.codecraft.webmagic.model.sources.Source.*;\nimport us.codecraft.webmagic.selector.*;\nimport us.codecraft.webmagic.utils.ClassUtils;\nimport us.codecraft.webmagic.utils.ExtractorUtils;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\nimport static us.codecraft.webmagic.model.annotation.ExtractBy.Source.RawText;\n\n/**\n * The main internal logic of page model extractor.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\nclass PageModelExtractor {\n\n    @Getter\n    private List<Pattern> targetUrlPatterns = new ArrayList<Pattern>();\n\n    @Getter\n    private Selector targetUrlRegionSelector;\n\n    @Getter\n    private List<Pattern> helpUrlPatterns = new ArrayList<Pattern>();\n\n    @Getter\n    private Selector helpUrlRegionSelector;\n\n    @Getter\n    private Class clazz;\n\n    private List<FieldExtractor> fieldExtractors;\n\n    private Extractor objectExtractor;\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    public static PageModelExtractor create(Class clazz) {\n        PageModelExtractor pageModelExtractor = new PageModelExtractor();\n        pageModelExtractor.init(clazz);\n        return pageModelExtractor;\n    }\n\n    private void init(Class clazz) {\n        this.clazz = clazz;\n        initClassExtractors();\n        fieldExtractors = new ArrayList<FieldExtractor>();\n        for (Field field : ClassUtils.getFieldsIncludeSuperClass(clazz)) {\n            field.setAccessible(true);\n            FieldExtractor fieldExtractor = getAnnotationExtractBy(clazz, field);\n            FieldExtractor fieldExtractorTmp = getAnnotationExtractCombo(clazz, field);\n            if (fieldExtractor != null && fieldExtractorTmp != null) {\n                throw new IllegalStateException(\"Only one of 'ExtractBy ComboExtract ExtractByUrl' can be added to a field!\");\n            } else if (fieldExtractor == null && fieldExtractorTmp != null) {\n                fieldExtractor = fieldExtractorTmp;\n            }\n            fieldExtractorTmp = getAnnotationExtractByUrl(clazz, field);\n            if (fieldExtractor != null && fieldExtractorTmp != null) {\n                throw new IllegalStateException(\"Only one of 'ExtractBy ComboExtract ExtractByUrl' can be added to a field!\");\n            } else if (fieldExtractor == null && fieldExtractorTmp != null) {\n                fieldExtractor = fieldExtractorTmp;\n            }\n            if (fieldExtractor != null) {\n                fieldExtractor.setObjectFormatter(new ObjectFormatterBuilder().setField(field).build());\n                fieldExtractors.add(fieldExtractor);\n            }\n        }\n    }\n\n    private FieldExtractor getAnnotationExtractByUrl(Class clazz, Field field) {\n        FieldExtractor fieldExtractor = null;\n        ExtractByUrl extractByUrl = field.getAnnotation(ExtractByUrl.class);\n        if (extractByUrl != null) {\n            String regexPattern = extractByUrl.value();\n            if (regexPattern.trim().equals(\"\")) {\n                regexPattern = \".*\";\n            }\n            fieldExtractor = new FieldExtractor(field,\n                    new RegexSelector(regexPattern), new Url(), extractByUrl.notNull(),\n                    extractByUrl.multi() || List.class.isAssignableFrom(field.getType()));\n            Method setterMethod = getSetterMethod(clazz, field);\n            if (setterMethod != null) {\n                fieldExtractor.setSetterMethod(setterMethod);\n            }\n        }\n        return fieldExtractor;\n    }\n\n    private FieldExtractor getAnnotationExtractCombo(Class clazz, Field field) {\n        FieldExtractor fieldExtractor = null;\n        ComboExtract comboExtract = field.getAnnotation(ComboExtract.class);\n        if (comboExtract != null) {\n            ExtractBy[] extractBies = comboExtract.value();\n            Selector selector;\n            switch (comboExtract.op()) {\n                case And:\n                    selector = new AndSelector(ExtractorUtils.getSelectors(extractBies));\n                    break;\n                case Or:\n                    selector = new OrSelector(ExtractorUtils.getSelectors(extractBies));\n                    break;\n                default:\n                    selector = new AndSelector(ExtractorUtils.getSelectors(extractBies));\n            }\n            fieldExtractor = new FieldExtractor(field, selector, comboExtract.source() == ComboExtract.Source.RawHtml ? new RawHtml() : new SelectedHtml(),\n                    comboExtract.notNull(), comboExtract.multi() || List.class.isAssignableFrom(field.getType()));\n            Method setterMethod = getSetterMethod(clazz, field);\n            if (setterMethod != null) {\n                fieldExtractor.setSetterMethod(setterMethod);\n            }\n        }\n        return fieldExtractor;\n    }\n\n    private FieldExtractor getAnnotationExtractBy(Class clazz, Field field) {\n        FieldExtractor fieldExtractor = null;\n        ExtractBy extractBy = field.getAnnotation(ExtractBy.class);\n        if (extractBy != null) {\n            Selector selector = ExtractorUtils.getSelector(extractBy);\n            ExtractBy.Source extractSource = extractBy.source();\n            if (extractBy.type()== ExtractBy.Type.JsonPath)\n                extractSource = RawText;\n            Source source = null;\n            switch (extractSource) {\n                case RawText:\n                    source = new RawText();\n                    break;\n                case RawHtml:\n                    source = new RawHtml();\n                    break;\n                case SelectedHtml:\n                    source = new SelectedHtml();\n                    break;\n                default:\n                    source = new SelectedHtml();\n            }\n            fieldExtractor = new FieldExtractor(field, selector, source,\n                    extractBy.notNull(), List.class.isAssignableFrom(field.getType()));\n            fieldExtractor.setSetterMethod(getSetterMethod(clazz, field));\n        }\n        return fieldExtractor;\n    }\n\n    public static Method getSetterMethod(Class clazz, Field field) {\n        String name = \"set\" + StringUtils.capitalize(field.getName());\n        try {\n            Method declaredMethod = clazz.getDeclaredMethod(name, field.getType());\n            declaredMethod.setAccessible(true);\n            return declaredMethod;\n        } catch (NoSuchMethodException e) {\n            return null;\n        }\n    }\n\n    private void initClassExtractors() {\n        Annotation annotation = clazz.getAnnotation(TargetUrl.class);\n        if (annotation == null) {\n            targetUrlPatterns.add(Pattern.compile(\".*\"));\n        } else {\n            TargetUrl targetUrl = (TargetUrl) annotation;\n            String[] value = targetUrl.value();\n            for (String s : value) {\n                targetUrlPatterns.add(Pattern.compile(s.replace(\".\", \"\\\\.\").replace(\"*\", \"[^\\\"'#]*\")));\n            }\n            if (!targetUrl.sourceRegion().equals(\"\")) {\n                targetUrlRegionSelector = new XpathSelector(targetUrl.sourceRegion());\n            }\n        }\n        annotation = clazz.getAnnotation(HelpUrl.class);\n        if (annotation != null) {\n            HelpUrl helpUrl = (HelpUrl) annotation;\n            String[] value = helpUrl.value();\n            for (String s : value) {\n                helpUrlPatterns.add(Pattern.compile(s.replace(\".\", \"\\\\.\").replace(\"*\", \"[^\\\"'#]*\")));\n            }\n            if (!helpUrl.sourceRegion().equals(\"\")) {\n                helpUrlRegionSelector = new XpathSelector(helpUrl.sourceRegion());\n            }\n        }\n        annotation = clazz.getAnnotation(ExtractBy.class);\n        if (annotation != null) {\n            ExtractBy extractBy = (ExtractBy) annotation;\n            objectExtractor = new Extractor(new XpathSelector(extractBy.value()), new SelectedHtml(), extractBy.notNull(), extractBy.multi());\n        }\n    }\n\n    public Object process(Page page) {\n        boolean matched = false;\n        for (Pattern targetPattern : targetUrlPatterns) {\n            if (targetPattern.matcher(page.getUrl().toString()).matches()) {\n                matched = true;\n            }\n        }\n        if (!matched) {\n            return null;\n        }\n        if (objectExtractor == null) {\n            return processSingle(page, null, true);\n        } else {\n            if (objectExtractor.multi) {\n                List<Object> os = new ArrayList<Object>();\n                List<String> list = objectExtractor.getSelector().selectList(page.getRawText());\n                for (String s : list) {\n                    Object o = processSingle(page, s, false);\n                    if (o != null) {\n                        os.add(o);\n                    }\n                }\n                return os;\n            } else {\n                String select = objectExtractor.getSelector().select(page.getRawText());\n                Object o = processSingle(page, select, false);\n                return o;\n            }\n        }\n    }\n\n    private Object processSingle(Page page, String html, boolean isRaw) {\n        Object o = null;\n        try {\n            o = clazz.newInstance();\n            for (FieldExtractor fieldExtractor : fieldExtractors) {\n                PageField field = SourceTextExtractor.getText(page, html, isRaw, fieldExtractor);\n                if (!field.operation(o, fieldExtractor, logger))\n                    return null;\n            }\n            if (AfterExtractor.class.isAssignableFrom(clazz))\n                ((AfterExtractor) o).afterProcess(page);\n        } catch (Exception e) {\n            logger.error(\"extract fail\", e);\n        }\n        return o;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/annotation/ComboExtract.java",
    "content": "package us.codecraft.webmagic.model.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\n/**\n * Combo 'ExtractBy' extractor with and/or operator.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.1\n */\n@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\n@Target({ElementType.FIELD, ElementType.TYPE})\npublic @interface ComboExtract {\n\n    /**\n     * The extractors to be combined.\n     *\n     * @return the extractors to be combined\n     */\n    ExtractBy[] value();\n\n    public static enum Op {\n        /**\n         * All extractors will be arranged as a pipeline. <br>\n         * The next extractor uses the result of the previous as source.\n         */\n        And,\n        /**\n         * All extractors will do extracting separately, <br>\n         * and the results of extractors will combined as the final result.\n         */\n        Or;\n    }\n\n    /**\n     * Combining operation of extractors.<br>\n     *\n     * @return combining operation of extractors\n     */\n    Op op() default Op.And;\n\n    /**\n     * Define whether the field can be null.<br>\n     * If set to 'true' and the extractor get no result, the entire class will be discarded. <br>\n     *\n     * @return whether the field can be null\n     */\n    boolean notNull() default false;\n\n    /**\n     * types of source for extracting.\n     */\n    public static enum Source {\n        /**\n         * extract from the content extracted by class extractor\n         */\n        SelectedHtml,\n        /**\n         * extract from the raw html\n         */\n        RawHtml\n    }\n\n    /**\n     * The source for extracting. <br>\n     * It works only if you already added 'ExtractBy' to Class. <br>\n     *\n     * @return the source for extracting\n     */\n    Source source() default Source.SelectedHtml;\n\n    /**\n     * Define whether the extractor return more than one result.\n     * When set to 'true', the extractor return a list of string (so you should define the field as List). <br>\n     *\n     * Deprecated since 0.4.2. This option is determined automatically by the class of field.\n     * @deprecated since 0.4.2\n     * @return whether the extractor return more than one result\n     */\n    boolean multi() default false;\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/annotation/ExtractBy.java",
    "content": "package us.codecraft.webmagic.model.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\n/**\n * Define the extractor for field or class.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\n@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\n@Target({ElementType.FIELD, ElementType.TYPE})\npublic @interface ExtractBy {\n\n    /**\n     * Extractor expression, support XPath, CSS Selector and regex.\n     *\n     * @return extractor expression\n     */\n    String value();\n\n    /**\n     * types of extractor expressions\n     */\n    public static enum Type {XPath, Regex, Css, JsonPath}\n\n    /**\n     * Extractor type, support XPath, CSS Selector and regex.\n     *\n     * @return extractor type\n     */\n    Type type() default Type.XPath;\n\n    /**\n     * Define whether the field can be null.<br>\n     * If set to 'true' and the extractor get no result, the entire class will be discarded. <br>\n     *\n     * @return whether the field can be null\n     */\n    boolean notNull() default false;\n\n    /**\n     * types of source for extracting.\n     */\n    public static enum Source {\n        /**\n         * extract from the content extracted by class extractor\n         */\n        SelectedHtml,\n        /**\n         * extract from the raw html\n         */\n        RawHtml,\n        RawText\n    }\n\n    /**\n     * The source for extracting. <br>\n     * It works only if you already added 'ExtractBy' to Class. <br>\n     *\n     * @return the source for extracting\n     */\n    Source source() default Source.SelectedHtml;\n\n    /**\n     * Define whether the extractor return more than one result.\n     * When set to 'true', the extractor return a list of string (so you should define the field as List). <br>\n     *\n     * Deprecated since 0.4.2. This option is determined automatically by the class of field.\n     * @deprecated since 0.4.2\n     * @return whether the extractor return more than one result\n     */\n    boolean multi() default false;\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/annotation/ExtractByUrl.java",
    "content": "package us.codecraft.webmagic.model.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\n/**\n * Define a extractor to extract data in url of current page. Only regex can be used. <br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\n@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\n@Target({ElementType.FIELD})\npublic @interface ExtractByUrl {\n\n    /**\n     * Extractor expression, only regex can be used\n     *\n     * @return extractor expression\n     */\n    String value() default \"\";\n\n    /**\n     * Define whether the field can be null.<br>\n     * If set to 'true' and the extractor get no result, the entire class will be discarded. <br>\n     *\n     * @return whether the field can be null\n     */\n    boolean notNull() default false;\n\n    /**\n     * Define whether the extractor return more than one result.\n     * When set to 'true', the extractor return a list of string (so you should define the field as List). <br>\n     *\n     * Deprecated since 0.4.2. This option is determined automatically by the class of field.\n     * @deprecated since 0.4.2\n     * @return whether the extractor return more than one result\n     */\n    boolean multi() default false;\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/annotation/Formatter.java",
    "content": "package us.codecraft.webmagic.model.annotation;\n\nimport us.codecraft.webmagic.model.formatter.ObjectFormatter;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\n/**\n * Define how the result string is convert to an object for field.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.3.2\n */\n@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\n@Target({ElementType.FIELD})\npublic @interface Formatter {\n\n    Class<ObjectFormatter> DEFAULT_FORMATTER = ObjectFormatter.class;\n\n    /**\n     * Set formatter params.\n     *\n     * @return formatter params\n     */\n    String[] value() default \"\";\n\n    /**\n     * Specific the class of field of class of elements in collection for field. <br>\n     * It is not necessary to be set because we can detect the class by class of field,\n     * unless you use a collection as a field. <br>\n     *\n     * @return the class of field\n     */\n    Class subClazz() default Void.class;\n\n    /**\n     * If there are more than one formatter for a class, just specify the implement.\n     * @return implement\n     */\n    Class<? extends ObjectFormatter> formatter() default ObjectFormatter.class;\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/annotation/HelpUrl.java",
    "content": "package us.codecraft.webmagic.model.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\n/**\n * Define the 'help' url patterns for class. <br>\n * All urls matching the pattern will be crawled and but not extracted for new objects. <br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\n@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\n@Target({ElementType.TYPE})\npublic @interface HelpUrl {\n\n    /**\n     * The url patterns to crawl. <br>\n     * Use regex expression with some changes: <br>\n     *      \".\" stand for literal character \".\" instead of \"any character\". <br>\n     *      \"*\" stand for any legal character for url in 0-n length ([^\"'#]*) instead of \"any length\". <br>\n     *\n     * @return the url patterns for class\n     */\n    String[] value();\n\n    /**\n     * Define the region for url extracting. <br>\n     * Only support XPath.<br>\n     * When sourceRegion is set, the urls will be extracted only from the region instead of entire content. <br>\n     *\n     * @return the region for url extracting\n     */\n    String sourceRegion() default \"\";\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/annotation/TargetUrl.java",
    "content": "package us.codecraft.webmagic.model.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\n/**\n * Define the url patterns for class. <br>\n * All urls matching the pattern will be crawled and extracted for new objects. <br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\n@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\n@Target({ElementType.TYPE})\npublic @interface TargetUrl {\n\n    /**\n     * The url patterns for class.<br>\n     * Use regex expression with some changes: <br>\n     *      \".\" stand for literal character \".\" instead of \"any character\". <br>\n     *      \"*\" stand for any legal character for url in 0-n length ([^\"'#]*) instead of \"any length\". <br>\n     *\n     * @return the url patterns for class\n     */\n    String[] value();\n\n    /**\n     * Define the region for url extracting. <br>\n     * Only support XPath.<br>\n     * When sourceRegion is set, the urls will be extracted only from the region instead of entire content. <br>\n     *\n     * @return the region for url extracting\n     */\n    String sourceRegion() default \"\";\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/annotation/package.html",
    "content": "<html>\n\t<body>\nAnnotations for defining a extractor.\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/fields/MultipleField.java",
    "content": "package us.codecraft.webmagic.model.fields;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.slf4j.Logger;\n\nimport lombok.Getter;\nimport us.codecraft.webmagic.model.FieldExtractor;\nimport us.codecraft.webmagic.model.formatter.ObjectFormatter;\n\npublic class MultipleField extends PageField {\n   @Getter\n   private List<String> fieldNames; \n\n   public MultipleField(List<String> fieldNames) {\n      this.fieldNames = fieldNames;\n   }\n   \n   public boolean operation(Object o, FieldExtractor fieldExtractor, Logger logger) throws IllegalAccessException, InvocationTargetException {\n      if ((this.fieldNames == null || this.fieldNames.size() == 0) && fieldExtractor.isNotNull())\n         return false;\n      if (fieldExtractor.getObjectFormatter() != null) {\n         List<Object> converted = this.convert(this.fieldNames, fieldExtractor.getObjectFormatter(), logger);\n         setField(o, fieldExtractor, converted);\n      }\n      else\n         setField(o, fieldExtractor, this.fieldNames);\n      return true;\n   }\n\n   private List<Object> convert(List<String> values, ObjectFormatter objectFormatter, Logger logger) {\n      List<Object> objects = new ArrayList<>();\n      for (String value : values) {\n          Object converted = this.convert(value, objectFormatter, logger);\n          if (converted != null)\n              objects.add(converted);\n      }\n      return objects;\n  }\n}"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/fields/PageField.java",
    "content": "package us.codecraft.webmagic.model.fields;\n\nimport java.lang.reflect.InvocationTargetException;\n\nimport org.slf4j.Logger;\n\nimport us.codecraft.webmagic.model.FieldExtractor;\nimport us.codecraft.webmagic.model.formatter.ObjectFormatter;\n\npublic abstract class PageField {\n   public abstract boolean operation(Object o, FieldExtractor fieldExtractor, Logger logger) throws IllegalAccessException, InvocationTargetException;\n\n   protected Object convert(String value, ObjectFormatter objectFormatter, Logger logger) {\n      try {\n         Object format = objectFormatter.format(value);\n         logger.debug(\"String {} is converted to {}\", value, format);\n         return format;\n      } catch (Exception e) {\n            logger.error(\"convert \" + value + \" to \" + objectFormatter.clazz() + \" error!\", e);\n      }\n      return null;\n   }\n\n   protected void setField(Object o, FieldExtractor fieldExtractor, Object value) throws IllegalAccessException, InvocationTargetException {\n      if (value != null) {\n         if (fieldExtractor.getSetterMethod() != null)\n            fieldExtractor.getSetterMethod().invoke(o, value);\n         fieldExtractor.getField().set(o, value);\n      }\n   }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/fields/SingleField.java",
    "content": "package us.codecraft.webmagic.model.fields;\n\nimport java.lang.reflect.InvocationTargetException;\n\nimport org.slf4j.Logger;\n\nimport lombok.Getter;\nimport us.codecraft.webmagic.model.FieldExtractor;\n\npublic class SingleField extends PageField {\n   @Getter\n   private String fieldName; \n\n   public SingleField(String fieldName) {\n      this.fieldName = fieldName;\n   }\n\n   public boolean operation(Object o, FieldExtractor fieldExtractor, Logger logger) throws IllegalAccessException, InvocationTargetException {\n      if (fieldExtractor.getObjectFormatter() != null) {\n         Object converted = this.convert(this.fieldName, fieldExtractor.getObjectFormatter(), logger);\n         if (converted == null && fieldExtractor.isNotNull())\n            return false;\n         setField(o, fieldExtractor, converted);\n      } else\n         setField(o, fieldExtractor, this.fieldName);\n      return true;\n   }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/formatter/BasicClassDetector.java",
    "content": "package us.codecraft.webmagic.model.formatter;\n\npublic interface BasicClassDetector {\n    Class<?> detectBasicClass(Class<?> type);\n}\n\nclass IntegerClassDetector implements BasicClassDetector {\n    @Override\n    public Class<?> detectBasicClass(Class<?> type) {\n        if (type.equals(Integer.TYPE) || type.equals(Integer.class)) {\n            return Integer.class;\n        }\n        return null;\n    }\n}\n\nclass LongClassDetector implements BasicClassDetector {\n    @Override\n    public Class<?> detectBasicClass(Class<?> type) {\n        if (type.equals(Long.TYPE) || type.equals(Long.class)) {\n            return Long.class;\n        }\n        return null;\n    }\n}\n\nclass DoubleClassDetector implements BasicClassDetector {\n    @Override\n    public Class<?> detectBasicClass(Class<?> type) {\n        if (type.equals(Double.TYPE) || type.equals(Double.class)) {\n            return Double.class;\n        }\n        return null;\n    }\n}\n\nclass FloatClassDetector implements BasicClassDetector {\n    @Override\n    public Class<?> detectBasicClass(Class<?> type) {\n        if (type.equals(Float.TYPE) || type.equals(Float.class)) {\n            return Float.class;\n        }\n        return null;\n    }\n}\n\nclass ShortClassDetector implements BasicClassDetector {\n    @Override\n    public Class<?> detectBasicClass(Class<?> type) {\n        if (type.equals(Short.TYPE) || type.equals(Short.class)) {\n            return Short.class;\n        }\n        return null;\n    }\n}\n\nclass CharacterClassDetector implements BasicClassDetector {\n    @Override\n    public Class<?> detectBasicClass(Class<?> type) {\n        if (type.equals(Character.TYPE) || type.equals(Character.class)) {\n            return Character.class;\n        }\n        return null;\n    }\n}\n\nclass ByteClassDetector implements BasicClassDetector {\n    @Override\n    public Class<?> detectBasicClass(Class<?> type) {\n        if (type.equals(Byte.TYPE) || type.equals(Byte.class)) {\n            return Byte.class;\n        }\n        return null;\n    }\n}\n\nclass BooleanClassDetector implements BasicClassDetector {\n    @Override\n    public Class<?> detectBasicClass(Class<?> type) {\n        if (type.equals(Boolean.TYPE) || type.equals(Boolean.class)) {\n            return Boolean.class;\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/formatter/BasicTypeFormatter.java",
    "content": "package us.codecraft.webmagic.model.formatter;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.3.2\n */\npublic abstract class BasicTypeFormatter<T> implements ObjectFormatter<T> {\n\n    @Override\n    public void initParam(String[] extra) {\n\n    }\n\n    @Override\n    public T format(String raw) throws Exception {\n        if (raw == null) {\n            return null;\n        }\n        raw = raw.trim();\n        return formatTrimmed(raw);\n    }\n\n    protected abstract T formatTrimmed(String raw) throws Exception;\n    public static final List<Class<? extends ObjectFormatter>> basicTypeFormatters = Arrays.<Class<? extends ObjectFormatter>>asList(IntegerFormatter.class,\n            LongFormatter.class, DoubleFormatter.class, FloatFormatter.class, ShortFormatter.class,\n            CharactorFormatter.class, ByteFormatter.class, BooleanFormatter.class);\n    public static final List<BasicClassDetector> basicClassDetector= Arrays.asList(new IntegerClassDetector(),\n            new LongClassDetector(),\n            new FloatClassDetector(),\n            new DoubleClassDetector(),\n            new ShortClassDetector(),\n            new ByteClassDetector(),\n            new BooleanClassDetector(),\n            new CharacterClassDetector());\n\n    public static Class<?> detectBasicClass(Class<?> type) {\n        for (BasicClassDetector detector : basicClassDetector) {\n            Class<?> detectedClass = detector.detectBasicClass(type);\n            if (detectedClass != null) {\n                return detectedClass;\n            }\n        }\n        return type;\n    }\n\n    public static class IntegerFormatter extends BasicTypeFormatter<Integer> {\n        @Override\n        public Integer formatTrimmed(String raw) throws Exception {\n            return Integer.parseInt(raw);\n        }\n\n        @Override\n        public Class<Integer> clazz() {\n            return Integer.class;\n        }\n    }\n\n    public static class LongFormatter extends BasicTypeFormatter<Long> {\n        @Override\n        public Long formatTrimmed(String raw) throws Exception {\n            return Long.parseLong(raw);\n        }\n\n        @Override\n        public Class<Long> clazz() {\n            return Long.class;\n        }\n    }\n\n    public static class DoubleFormatter extends BasicTypeFormatter<Double> {\n        @Override\n        public Double formatTrimmed(String raw) throws Exception {\n            return Double.parseDouble(raw);\n        }\n\n        @Override\n        public Class<Double> clazz() {\n            return Double.class;\n        }\n    }\n\n    public static class FloatFormatter extends BasicTypeFormatter<Float> {\n        @Override\n        public Float formatTrimmed(String raw) throws Exception {\n            return Float.parseFloat(raw);\n        }\n\n        @Override\n        public Class<Float> clazz() {\n            return Float.class;\n        }\n    }\n\n    public static class ShortFormatter extends BasicTypeFormatter<Short> {\n        @Override\n        public Short formatTrimmed(String raw) throws Exception {\n            return Short.parseShort(raw);\n        }\n\n        @Override\n        public Class<Short> clazz() {\n            return Short.class;\n        }\n    }\n\n    public static class CharactorFormatter extends BasicTypeFormatter<Character> {\n        @Override\n        public Character formatTrimmed(String raw) throws Exception {\n            return raw.charAt(0);\n        }\n\n        @Override\n        public Class<Character> clazz() {\n            return Character.class;\n        }\n    }\n\n    public static class ByteFormatter extends BasicTypeFormatter<Byte> {\n        @Override\n        public Byte formatTrimmed(String raw) throws Exception {\n            return Byte.parseByte(raw, 10);\n        }\n\n        @Override\n        public Class<Byte> clazz() {\n            return Byte.class;\n        }\n    }\n\n    public static class BooleanFormatter extends BasicTypeFormatter<Boolean> {\n        @Override\n        public Boolean formatTrimmed(String raw) throws Exception {\n            return Boolean.parseBoolean(raw);\n        }\n\n        @Override\n        public Class<Boolean> clazz() {\n            return Boolean.class;\n        }\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/formatter/DateFormatter.java",
    "content": "package us.codecraft.webmagic.model.formatter;\n\nimport org.apache.commons.lang3.time.DateUtils;\n\nimport java.util.Date;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.3.2\n */\npublic class DateFormatter implements ObjectFormatter<Date> {\n\n    public static final String[] DEFAULT_PATTERN = new String[]{\"yyyy-MM-dd HH:mm\"};\n    private String[] datePatterns = DEFAULT_PATTERN;\n\n    @Override\n    public Date format(String raw) throws Exception {\n        return DateUtils.parseDate(raw, datePatterns);\n    }\n\n    @Override\n    public Class<Date> clazz() {\n        return Date.class;\n    }\n\n    @Override\n    public void initParam(String[] extra) {\n        if (extra != null && !(extra.length == 1 && extra[0].length() == 0)) {\n            datePatterns = extra;\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/formatter/ObjectFormatter.java",
    "content": "package us.codecraft.webmagic.model.formatter;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic interface ObjectFormatter<T> {\n\n    T format(String raw) throws Exception;\n\n    Class<T> clazz();\n\n    void initParam(String[] extra);\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/formatter/ObjectFormatterBuilder.java",
    "content": "package us.codecraft.webmagic.model.formatter;\n\nimport us.codecraft.webmagic.model.annotation.Formatter;\n\nimport java.lang.reflect.Field;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.7.0\n *         Date: 2017/6/3\n */\npublic class ObjectFormatterBuilder {\n\n    private Field field;\n\n    public ObjectFormatterBuilder setField(Field field) {\n        this.field = field;\n        return this;\n    }\n\n    private ObjectFormatter initFormatterForType(Class<?> fieldClazz, String[] params) {\n        if (fieldClazz.equals(String.class) || List.class.isAssignableFrom(fieldClazz)){\n            return null;\n        }\n        Class<? extends ObjectFormatter> formatterClass = ObjectFormatters.get(BasicTypeFormatter.detectBasicClass(fieldClazz));\n        if (formatterClass == null) {\n            throw new IllegalStateException(\"Can't find formatter for field \" + field.getName() + \" of type \" + fieldClazz);\n        }\n        return initFormatter(formatterClass, params);\n    }\n\n    private ObjectFormatter initFormatter(Class<? extends ObjectFormatter> formatterClazz, String[] params) {\n        try {\n            ObjectFormatter objectFormatter = formatterClazz.newInstance();\n            objectFormatter.initParam(params);\n            return objectFormatter;\n        } catch (InstantiationException e) {\n            throw new RuntimeException(e);\n        } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public ObjectFormatter build() {\n        Formatter formatter = field.getAnnotation(Formatter.class);\n        if (formatter != null && !formatter.formatter().equals(Formatter.DEFAULT_FORMATTER)) {\n            return initFormatter(formatter.formatter(), formatter.value());\n        }\n        if (formatter == null || formatter.subClazz().equals(Void.class)) {\n            return initFormatterForType(field.getType(), formatter != null ? formatter.value() : null);\n        } else {\n            return initFormatterForType(formatter.subClazz(), formatter.value());\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/formatter/ObjectFormatters.java",
    "content": "package us.codecraft.webmagic.model.formatter;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.3.2\n */\npublic class ObjectFormatters {\n\n    private static Map<Class, Class<? extends ObjectFormatter>> formatterMap = new ConcurrentHashMap<Class, Class<? extends ObjectFormatter>>();\n\n    static {\n        for (Class<? extends ObjectFormatter> basicTypeFormatter : BasicTypeFormatter.basicTypeFormatters) {\n            put(basicTypeFormatter);\n        }\n        put(DateFormatter.class);\n    }\n\n    public static void put(Class<? extends ObjectFormatter> objectFormatter) {\n        try {\n            formatterMap.put(objectFormatter.newInstance().clazz(), objectFormatter);\n        } catch (InstantiationException e) {\n            throw new RuntimeException(e);\n        } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static Class<? extends ObjectFormatter> get(Class<?> clazz){\n        return formatterMap.get(clazz);\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/package.html",
    "content": "<html>\n\t<body>\nPage model and annotations used to customize a crawler.\n\t</body>\n</html>\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/sources/Source.java",
    "content": "package us.codecraft.webmagic.model.sources;\n\nimport java.util.List;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.model.FieldExtractor;\n\npublic interface Source {\n   public String getText(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor);\n   public List<String> getTextList(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor);\n\n   public class RawHtml implements Source {\n      public String getText(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         return page.getHtml().selectDocument(fieldExtractor.getSelector());\n      }\n   \n      public List<String> getTextList(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         return page.getHtml().selectDocumentForList(fieldExtractor.getSelector());\n      }\n   }\n   \n   public class SelectedHtml implements Source {\n      public String getText(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         if (isRaw)\n            return page.getHtml().selectDocument(fieldExtractor.getSelector());\n         else\n            return fieldExtractor.getSelector().select(html);\n      }\n   \n      public List<String> getTextList(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         if (isRaw)\n            return page.getHtml().selectDocumentForList(fieldExtractor.getSelector());\n         else\n            return fieldExtractor.getSelector().selectList(html);\n      }\n   }\n   \n   public class Url implements Source {\n      public String getText(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         return fieldExtractor.getSelector().select(page.getUrl().toString());\n      }\n   \n      public List<String> getTextList(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         return fieldExtractor.getSelector().selectList(page.getUrl().toString());\n      }\n   }\n   \n   public class RawText implements Source {\n      public String getText(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         return fieldExtractor.getSelector().select(page.getRawText());\n      }\n   \n      public List<String> getTextList(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         return fieldExtractor.getSelector().selectList(page.getRawText());\n      }\n   }\n   \n   public class DefaultSource implements Source {\n      public String getText(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         return fieldExtractor.getSelector().select(html);\n      }\n   \n      public List<String> getTextList(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n         return fieldExtractor.getSelector().selectList(html);\n      }\n   }\n}\n\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/model/sources/SourceTextExtractor.java",
    "content": "package us.codecraft.webmagic.model.sources;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.model.FieldExtractor;\nimport us.codecraft.webmagic.model.fields.MultipleField;\nimport us.codecraft.webmagic.model.fields.PageField;\nimport us.codecraft.webmagic.model.fields.SingleField;\n\npublic class SourceTextExtractor {\n   public static PageField getText(Page page, String html, boolean isRaw, FieldExtractor fieldExtractor) {\n      Source source = fieldExtractor.getSource();\n      if (fieldExtractor.isMulti())\n         return new MultipleField(source.getTextList(page, html, isRaw, fieldExtractor));\n      else\n         return new SingleField(source.getText(page, html, isRaw, fieldExtractor));\n   }\n}"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/monitor/SpiderMonitor.java",
    "content": "package us.codecraft.webmagic.monitor;\n\nimport java.lang.management.ManagementFactory;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport javax.management.InstanceAlreadyExistsException;\nimport javax.management.JMException;\nimport javax.management.MBeanRegistrationException;\nimport javax.management.MBeanServer;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.NotCompliantMBeanException;\nimport javax.management.ObjectName;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.SpiderListener;\nimport us.codecraft.webmagic.utils.Experimental;\nimport us.codecraft.webmagic.utils.UrlUtils;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\n@Experimental\npublic class SpiderMonitor {\n\n    private static final SpiderMonitor INSTANCE = new SpiderMonitor();\n\n    private MBeanServer mbeanServer;\n\n    private String jmxServerName;\n\n    private List<SpiderStatusMXBean> spiderStatuses = new ArrayList<>();\n\n    protected SpiderMonitor() {\n        jmxServerName = \"WebMagic\";\n        mbeanServer = ManagementFactory.getPlatformMBeanServer();\n    }\n\n    /**\n     * Register spider for monitor.\n     *\n     * @param spiders spiders\n     * @return this\n     * @throws JMException JMException\n     */\n    public synchronized SpiderMonitor register(Spider... spiders) throws JMException {\n        for (Spider spider : spiders) {\n            MonitorSpiderListener monitorSpiderListener = new MonitorSpiderListener();\n            if (spider.getSpiderListeners() == null) {\n                List<SpiderListener> spiderListeners = new ArrayList<>();\n                spiderListeners.add(monitorSpiderListener);\n                spider.setSpiderListeners(spiderListeners);\n            } else {\n                spider.getSpiderListeners().add(monitorSpiderListener);\n            }\n            SpiderStatusMXBean spiderStatusMBean = getSpiderStatusMBean(spider, monitorSpiderListener);\n            registerMBean(spiderStatusMBean);\n            spiderStatuses.add(spiderStatusMBean);\n        }\n        return this;\n    }\n\n    protected SpiderStatusMXBean getSpiderStatusMBean(Spider spider, MonitorSpiderListener monitorSpiderListener) {\n        return new SpiderStatus(spider, monitorSpiderListener);\n    }\n\n    protected List<SpiderStatusMXBean> getSpiderStatuses() {\n        return this.spiderStatuses;\n    }\n\n    public static SpiderMonitor instance() {\n        return INSTANCE;\n    }\n\n    public class MonitorSpiderListener implements SpiderListener {\n\n        private final AtomicInteger successCount = new AtomicInteger(0);\n\n        private final AtomicInteger errorCount = new AtomicInteger(0);\n\n        private List<String> errorUrls = Collections.synchronizedList(new ArrayList<String>());\n\n        @Override\n        public void onSuccess(Request request) {\n            successCount.incrementAndGet();\n        }\n\n        @Override\n        public void onError(Request request, Exception e) {\n            errorUrls.add(request.getUrl());\n            errorCount.incrementAndGet();\n        }\n\n        public AtomicInteger getSuccessCount() {\n            return successCount;\n        }\n\n        public AtomicInteger getErrorCount() {\n            return errorCount;\n        }\n\n        public List<String> getErrorUrls() {\n            return errorUrls;\n        }\n    }\n\n    protected void registerMBean(SpiderStatusMXBean spiderStatus) throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {\n        ObjectName objName = new ObjectName(jmxServerName + \":name=\" + UrlUtils.removePort(spiderStatus.getName()));\n        mbeanServer.registerMBean(spiderStatus, objName);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/monitor/SpiderStatus.java",
    "content": "package us.codecraft.webmagic.monitor;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.scheduler.MonitorableScheduler;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic class SpiderStatus implements SpiderStatusMXBean {\n\n    protected final Spider spider;\n\n    protected Logger logger = LoggerFactory.getLogger(getClass());\n\n    protected final SpiderMonitor.MonitorSpiderListener monitorSpiderListener;\n\n    public SpiderStatus(Spider spider, SpiderMonitor.MonitorSpiderListener monitorSpiderListener) {\n        this.spider = spider;\n        this.monitorSpiderListener = monitorSpiderListener;\n    }\n\n    public String getName() {\n        return spider.getUUID();\n    }\n\n    public int getLeftPageCount() {\n        if (spider.getScheduler() instanceof MonitorableScheduler) {\n            return ((MonitorableScheduler) spider.getScheduler()).getLeftRequestsCount(spider);\n        }\n        logger.warn(\"Get leftPageCount fail, try to use a Scheduler implement MonitorableScheduler for monitor count!\");\n        return -1;\n    }\n\n    public int getTotalPageCount() {\n        if (spider.getScheduler() instanceof MonitorableScheduler) {\n            return ((MonitorableScheduler) spider.getScheduler()).getTotalRequestsCount(spider);\n        }\n        logger.warn(\"Get totalPageCount fail, try to use a Scheduler implement MonitorableScheduler for monitor count!\");\n        return -1;\n    }\n\n    @Override\n    public int getSuccessPageCount() {\n        return monitorSpiderListener.getSuccessCount().get();\n    }\n\n    @Override\n    public int getErrorPageCount() {\n        return monitorSpiderListener.getErrorCount().get();\n    }\n\n    public List<String> getErrorPages() {\n        return monitorSpiderListener.getErrorUrls();\n    }\n\n    @Override\n    public String getStatus() {\n        return spider.getStatus().name();\n    }\n\n    @Override\n    public int getThread() {\n        return spider.getThreadAlive();\n    }\n\n    public void start() {\n        spider.start();\n    }\n\n    public void stop() {\n        spider.stop();\n    }\n\n    @Override\n    public Date getStartTime() {\n        return spider.getStartTime();\n    }\n\n    @Override\n    public int getPagePerSecond() {\n        if (getStartTime() != null) {\n            int runSeconds = (int) (System.currentTimeMillis() - getStartTime().getTime()) / 1000;\n            if (runSeconds != 0) {\n                return getSuccessPageCount() / runSeconds;\n            }\n        }\n        return -1;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/monitor/SpiderStatusMXBean.java",
    "content": "package us.codecraft.webmagic.monitor;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic interface SpiderStatusMXBean {\n\n    public String getName();\n\n    public String getStatus();\n\n    public int getThread();\n\n    public int getTotalPageCount();\n\n    public int getLeftPageCount();\n\n    public int getSuccessPageCount();\n\n    public int getErrorPageCount();\n\n    public List<String> getErrorPages();\n\n    public void start();\n\n    public void stop();\n\n    public Date getStartTime();\n\n    public int getPagePerSecond();\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/pipeline/CollectorPageModelPipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport us.codecraft.webmagic.Task;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class CollectorPageModelPipeline<T> implements PageModelPipeline<T> {\n\n    private List<T> collected = new ArrayList<T>();\n\n    @Override\n    public synchronized void process(T t, Task task) {\n        collected.add(t);\n    }\n\n    public List<T> getCollected() {\n        return collected;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/pipeline/FilePageModelPipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.model.HasKey;\nimport us.codecraft.webmagic.utils.FilePersistentBase;\n\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\n/**\n * Store results objects (page models) to files in plain format.<br>\n * Use model.getKey() as file name if the model implements HasKey.<br>\n * Otherwise use SHA1 as file name.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.3.0\n */\npublic class FilePageModelPipeline extends FilePersistentBase implements PageModelPipeline {\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    /**\n     * new JsonFilePageModelPipeline with default path \"/data/webmagic/\"\n     */\n    public FilePageModelPipeline() {\n        setPath(\"/data/webmagic/\");\n    }\n\n    public FilePageModelPipeline(String path) {\n        setPath(path);\n    }\n\n    @Override\n    public void process(Object o, Task task) {\n        String path = this.path + PATH_SEPERATOR + task.getUUID() + PATH_SEPERATOR;\n        try {\n            String filename;\n            if (o instanceof HasKey) {\n                filename = path + ((HasKey) o).key() + \".html\";\n            } else {\n                filename = path + DigestUtils.md5Hex(ToStringBuilder.reflectionToString(o)) + \".html\";\n            }\n            PrintWriter printWriter = new PrintWriter(new FileWriter(getFile(filename)));\n            printWriter.write(ToStringBuilder.reflectionToString(o));\n            printWriter.close();\n        } catch (IOException e) {\n            logger.warn(\"write file error\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/pipeline/JsonFilePageModelPipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport com.alibaba.fastjson.JSON;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.model.HasKey;\nimport us.codecraft.webmagic.utils.FilePersistentBase;\n\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\n/**\n * Store results objects (page models) to files in JSON format.<br>\n * Use model.getKey() as file name if the model implements HasKey.<br>\n * Otherwise use SHA1 as file name.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class JsonFilePageModelPipeline extends FilePersistentBase implements PageModelPipeline {\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    /**\n     * new JsonFilePageModelPipeline with default path \"/data/webmagic/\"\n     */\n    public JsonFilePageModelPipeline() {\n        setPath(\"/data/webmagic/\");\n    }\n\n    public JsonFilePageModelPipeline(String path) {\n        setPath(path);\n    }\n\n    @Override\n    public void process(Object o, Task task) {\n        String path = this.path + PATH_SEPERATOR + task.getUUID() + PATH_SEPERATOR;\n        try {\n            String filename;\n            if (o instanceof HasKey) {\n                filename = path + ((HasKey) o).key() + \".json\";\n            } else {\n                filename = path + DigestUtils.md5Hex(ToStringBuilder.reflectionToString(o)) + \".json\";\n            }\n            PrintWriter printWriter = new PrintWriter(new FileWriter(getFile(filename)));\n            printWriter.write(JSON.toJSONString(o));\n            printWriter.close();\n        } catch (IOException e) {\n            logger.warn(\"write file error\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/pipeline/JsonFilePipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport com.alibaba.fastjson.JSON;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.utils.FilePersistentBase;\n\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\n/**\n * Store results to files in JSON format.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class JsonFilePipeline extends FilePersistentBase implements Pipeline {\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    /**\n     * new JsonFilePageModelPipeline with default path \"/data/webmagic/\"\n     */\n    public JsonFilePipeline() {\n        setPath(\"/data/webmagic\");\n    }\n\n    public JsonFilePipeline(String path) {\n        setPath(path);\n    }\n\n    @Override\n    public void process(ResultItems resultItems, Task task) {\n        String path = this.path + PATH_SEPERATOR + task.getUUID() + PATH_SEPERATOR;\n        try {\n            PrintWriter printWriter = new PrintWriter(new FileWriter(getFile(path + DigestUtils.md5Hex(resultItems.getRequest().getUrl()) + \".json\")));\n            printWriter.write(JSON.toJSONString(resultItems.getAll()));\n            printWriter.close();\n        } catch (IOException e) {\n            logger.warn(\"write file error\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/pipeline/MultiPagePipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport us.codecraft.webmagic.MultiPageModel;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.utils.Experimental;\nimport us.codecraft.webmagic.utils.DoubleKeyMap;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * A pipeline combines the result in more than one page together.<br>\n * Used for news and articles containing more than one web page. <br>\n * MultiPagePipeline will store parts of object and output them when all parts are extracted.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\n@Experimental\npublic class MultiPagePipeline implements Pipeline {\n\n    private DoubleKeyMap<String, String, Boolean> pageMap = new DoubleKeyMap<String, String, Boolean>(ConcurrentHashMap.class);\n\n    private DoubleKeyMap<String, String, MultiPageModel> objectMap = new DoubleKeyMap<String, String, MultiPageModel>(ConcurrentHashMap.class);\n\n    @Override\n    public void process(ResultItems resultItems, Task task) {\n        Map<String, Object> resultItemsAll = resultItems.getAll();\n        Iterator<Map.Entry<String, Object>> iterator = resultItemsAll.entrySet().iterator();\n        while (iterator.hasNext()) {\n            handleObject(iterator);\n        }\n    }\n\n    private void handleObject(Iterator<Map.Entry<String, Object>> iterator) {\n        Map.Entry<String, Object> objectEntry = iterator.next();\n        Object o = objectEntry.getValue();\n        //需要拼凑\n        if (o instanceof MultiPageModel) {\n            MultiPageModel multiPageModel = (MultiPageModel) o;\n            //这次处理的部分，设置为完成\n            pageMap.put(multiPageModel.getPageKey(), multiPageModel.getPage(), Boolean.FALSE);\n            //每个key单独加锁\n            synchronized (pageMap.get(multiPageModel.getPageKey())) {\n                pageMap.put(multiPageModel.getPageKey(), multiPageModel.getPage(), Boolean.TRUE);\n                //其他需要拼凑的部分\n                if (multiPageModel.getOtherPages() != null) {\n                    for (String otherPage : multiPageModel.getOtherPages()) {\n                        Boolean aBoolean = pageMap.get(multiPageModel.getPageKey(), otherPage);\n                        if (aBoolean == null) {\n                            pageMap.put(multiPageModel.getPageKey(), otherPage, Boolean.FALSE);\n                        }\n                    }\n                }\n                //check if all pages are processed\n                Map<String, Boolean> booleanMap = pageMap.get(multiPageModel.getPageKey());\n                objectMap.put(multiPageModel.getPageKey(), multiPageModel.getPage(), multiPageModel);\n                if (booleanMap == null) {\n                    return;\n                }\n                // /过滤，这次完成的page item中，还未拼凑完整的item，不进入下一个pipeline\n                for (Map.Entry<String, Boolean> stringBooleanEntry : booleanMap.entrySet()) {\n                    if (!stringBooleanEntry.getValue()) {\n                        iterator.remove();\n                        return;\n                    }\n                }\n                List<Map.Entry<String, MultiPageModel>> entryList = new ArrayList<Map.Entry<String, MultiPageModel>>();\n                entryList.addAll(objectMap.get(multiPageModel.getPageKey()).entrySet());\n                if (entryList.size() != 0) {\n                    Collections.sort(entryList, new Comparator<Map.Entry<String, MultiPageModel>>() {\n                        @Override\n                        public int compare(Map.Entry<String, MultiPageModel> o1, Map.Entry<String, MultiPageModel> o2) {\n                            try {\n                                int i1 = Integer.parseInt(o1.getKey());\n                                int i2 = Integer.parseInt(o2.getKey());\n                                return i1 - i2;\n                            } catch (NumberFormatException e) {\n                                return o1.getKey().compareTo(o2.getKey());\n                            }\n                        }\n                    });\n                    // 合并\n                    MultiPageModel value = entryList.get(0).getValue();\n                    for (int i = 1; i < entryList.size(); i++) {\n                        value = value.combine(entryList.get(i).getValue());\n                    }\n                    objectEntry.setValue(value);\n                }\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/pipeline/PageModelPipeline.java",
    "content": "package us.codecraft.webmagic.pipeline;\n\nimport us.codecraft.webmagic.Task;\n\n/**\n * Implements PageModelPipeline to persistent your page model.\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic interface PageModelPipeline<T> {\n\n    public void process(T t, Task task);\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/scheduler/BloomFilterDuplicateRemover.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 16/12/18\n *         Time: 上午10:23\n */\n\nimport com.google.common.hash.BloomFilter;\nimport com.google.common.hash.Funnels;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.scheduler.component.DuplicateRemover;\n\nimport java.nio.charset.Charset;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * BloomFilterDuplicateRemover for huge number of urls.\n *\n * @author code4crafer@gmail.com\n * @since 0.5.1\n */\npublic class BloomFilterDuplicateRemover implements DuplicateRemover {\n\n    private int expectedInsertions;\n\n    private double fpp;\n\n    private AtomicInteger counter;\n\n    public BloomFilterDuplicateRemover(int expectedInsertions) {\n        this(expectedInsertions, 0.01);\n    }\n\n    /**\n     *\n     * @param expectedInsertions the number of expected insertions to the constructed\n     * @param fpp the desired false positive probability (must be positive and less than 1.0)\n     */\n    public BloomFilterDuplicateRemover(int expectedInsertions, double fpp) {\n        this.expectedInsertions = expectedInsertions;\n        this.fpp = fpp;\n        this.bloomFilter = rebuildBloomFilter();\n    }\n\n    protected BloomFilter<CharSequence> rebuildBloomFilter() {\n        counter = new AtomicInteger(0);\n        return BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp);\n    }\n\n    private final BloomFilter<CharSequence> bloomFilter;\n\n    @Override\n    public boolean isDuplicate(Request request, Task task) {\n        boolean isDuplicate = bloomFilter.mightContain(getUrl(request));\n        if (!isDuplicate) {\n            bloomFilter.put(getUrl(request));\n            counter.incrementAndGet();\n        }\n        return isDuplicate;\n    }\n\n    protected String getUrl(Request request) {\n        return request.getUrl();\n    }\n\n    @Override\n    public void resetDuplicateCheck(Task task) {\n        rebuildBloomFilter();\n    }\n\n    @Override\n    public int getTotalRequestsCount(Task task) {\n        return counter.get();\n    }\n}"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/scheduler/FileCacheQueueScheduler.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport org.apache.commons.lang3.math.NumberUtils;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\n\nimport java.io.*;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n\n/**\n * Store urls and cursor in files so that a Spider can resume the status when shutdown.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class FileCacheQueueScheduler extends DuplicateRemovedScheduler implements MonitorableScheduler, Closeable {\n\n    private String filePath = System.getProperty(\"java.io.tmpdir\");\n\n    private String fileUrlAllName = \".urls.txt\";\n\n    private Task task;\n\n    private String fileCursor = \".cursor.txt\";\n\n    private PrintWriter fileUrlWriter;\n\n    private PrintWriter fileCursorWriter;\n\n    private AtomicInteger cursor = new AtomicInteger();\n\n    private AtomicBoolean inited = new AtomicBoolean(false);\n\n    private BlockingQueue<Request> queue;\n\n    private ScheduledExecutorService flushThreadPool;\n\n    public FileCacheQueueScheduler(String filePath) {\n        if (!filePath.endsWith(\"/\") && !filePath.endsWith(\"\\\\\")) {\n            filePath += \"/\";\n        }\n        this.filePath = filePath;\n        initDuplicateRemover();\n    }\n\n    private void flush() {\n        fileUrlWriter.flush();\n        fileCursorWriter.flush();\n    }\n\n    private void init(Task task) {\n        this.task = task;\n        File file = new File(filePath);\n        if (!file.exists()) {\n            file.mkdirs();\n        }\n        readFile();\n        initWriter();\n        initFlushThread();\n        inited.set(true);\n        logger.info(\"init cache scheduler success\");\n    }\n\n    private void initDuplicateRemover() {\n        BloomFilterDuplicateRemover bloomFilterDuplicateRemover = new BloomFilterDuplicateRemover(this.filePath.hashCode());\n        setDuplicateRemover(bloomFilterDuplicateRemover);\n    }\n\n    private void initFlushThread() {\n        flushThreadPool = Executors.newScheduledThreadPool(1);\n        flushThreadPool.scheduleAtFixedRate(this::flush, 10, 10, TimeUnit.SECONDS);\n    }\n\n    private void initWriter() {\n        try {\n            fileUrlWriter = new PrintWriter(new FileWriter(getFileName(fileUrlAllName), true));\n            fileCursorWriter = new PrintWriter(new FileWriter(getFileName(fileCursor), false));\n        } catch (IOException e) {\n            throw new RuntimeException(\"init cache scheduler error\", e);\n        }\n    }\n\n    private void readFile() {\n        try {\n            queue = new LinkedBlockingQueue<Request>();\n            readCursorFile();\n            readUrlFile();\n            // initDuplicateRemover();\n        } catch (FileNotFoundException e) {\n            //init\n            logger.info(\"init cache file \" + getFileName(fileUrlAllName));\n        } catch (IOException e) {\n            logger.error(\"init file error\", e);\n        }\n    }\n\n    private void readUrlFile() throws IOException {\n        try (BufferedReader fileUrlReader = new BufferedReader(new FileReader(getFileName(fileUrlAllName)))) {\n            String line;\n            int lineReaded = 0;\n            while ((line = fileUrlReader.readLine()) != null) {\n                Request request = deserializeRequest(line);\n                this.getDuplicateRemover().isDuplicate(request, null);\n                lineReaded++;\n                if (lineReaded > cursor.get()) {\n                    queue.add(request);\n                }\n            }\n        }\n    }\n\n    private void readCursorFile() throws IOException {\n        String fileName = getFileName(fileCursor);\n        try (BufferedReader fileCursorReader = new BufferedReader(new FileReader(fileName))) {\n            String line;\n            String lastLine = null;\n            //read the last number\n            while ((line = fileCursorReader.readLine()) != null) {\n                line = line.trim();\n                if (!line.isEmpty()) {\n                    lastLine = line;\n                }\n            }\n            if (lastLine != null) {\n                cursor.set(NumberUtils.toInt(lastLine));\n            }\n        }\n    }\n\n    public void close() throws IOException {\n        flushThreadPool.shutdown();\n        fileUrlWriter.close();\n        fileCursorWriter.close();\n    }\n\n    private String getFileName(String filename) {\n        return filePath + task.getUUID() + filename;\n    }\n\n    @Override\n    protected void pushWhenNoDuplicate(Request request, Task task) {\n        if (!inited.get()) {\n            init(task);\n        }\n        queue.add(request);\n        fileUrlWriter.println(serializeRequest(request));\n    }\n\n    @Override\n    public synchronized Request poll(Task task) {\n        if (!inited.get()) {\n            init(task);\n        }\n        fileCursorWriter.println(cursor.incrementAndGet());\n        return queue.poll();\n    }\n\n    @Override\n    public int getLeftRequestsCount(Task task) {\n        return queue.size();\n    }\n\n    @Override\n    public int getTotalRequestsCount(Task task) {\n        return getDuplicateRemover().getTotalRequestsCount(task);\n    }\n\n    protected String serializeRequest(Request request) {\n        return request.getUrl();\n    }\n\n    protected Request deserializeRequest(String line) {\n        return new Request(line);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/scheduler/RedisPriorityScheduler.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport java.util.Set;\n\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.alibaba.fastjson.JSON;\n\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisPool;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\n\n/**\n * the redis scheduler with priority\n * @author sai\n * Created by sai on 16-5-27.\n */\npublic class RedisPriorityScheduler extends RedisScheduler {\n\n    private static final String ZSET_PREFIX = \"zset_\";\n\n    private static final String QUEUE_PREFIX = \"queue_\";\n\n    private static final String NO_PRIORITY_SUFFIX = \"_zore\";\n\n    private static final String PLUS_PRIORITY_SUFFIX    = \"_plus\";\n\n    private static final String MINUS_PRIORITY_SUFFIX   = \"_minus\";\n\n    public RedisPriorityScheduler(String host) {\n        super(host);\n    }\n\n    public RedisPriorityScheduler(JedisPool pool) {\n        super(pool);\n    }\n\n    @Override\n    protected void pushWhenNoDuplicate(Request request, Task task) {\n        try (Jedis jedis = pool.getResource()) {\n            if (request.getPriority() > 0) {\n                jedis.zadd(getZsetPlusPriorityKey(task), request.getPriority(), request.getUrl());\n            } else if (request.getPriority() < 0) {\n                jedis.zadd(getZsetMinusPriorityKey(task), request.getPriority(), request.getUrl());\n            } else {\n                jedis.lpush(getQueueNoPriorityKey(task), request.getUrl());\n            }\n\n            setExtrasInItem(jedis, request, task);\n        }\n    }\n\n    @Override\n    public synchronized Request poll(Task task) {\n        try (Jedis jedis = pool.getResource()) {\n            String url = getRequest(jedis, task);\n            if (StringUtils.isBlank(url)) {\n                return null;\n            }\n            return getExtrasInItem(jedis, url, task);\n        }\n    }\n\n    private String getRequest(Jedis jedis, Task task) {\n        String url;\n        Set<String> urls = jedis.zrevrange(getZsetPlusPriorityKey(task), 0, 0);\n        if (urls.isEmpty()) {\n            url = jedis.lpop(getQueueNoPriorityKey(task));\n            if (StringUtils.isBlank(url)) {\n                urls = jedis.zrevrange(getZsetMinusPriorityKey(task), 0, 0);\n                if (!urls.isEmpty()) {\n                    url = urls.toArray(new String[0])[0];\n                    jedis.zrem(getZsetMinusPriorityKey(task), url);\n                }\n            }\n        } else {\n            url = urls.toArray(new String[0])[0];\n            jedis.zrem(getZsetPlusPriorityKey(task), url);\n        }\n        return url;\n    }\n\n    @Override\n    public void resetDuplicateCheck(Task task) {\n        try (Jedis jedis = pool.getResource()) {\n            jedis.del(getSetKey(task));\n        }\n    }\n\n    private String getZsetPlusPriorityKey(Task task) {\n        return ZSET_PREFIX + task.getUUID() + PLUS_PRIORITY_SUFFIX;\n    }\n\n    private String getQueueNoPriorityKey(Task task) {\n        return QUEUE_PREFIX + task.getUUID() + NO_PRIORITY_SUFFIX;\n    }\n\n    private String getZsetMinusPriorityKey(Task task) {\n        return ZSET_PREFIX + task.getUUID() + MINUS_PRIORITY_SUFFIX;\n    }\n\n    private void setExtrasInItem(Jedis jedis,Request request, Task task) {\n        if (!request.getExtras().isEmpty()) {\n            String field = DigestUtils.sha1Hex(request.getUrl());\n            String value = JSON.toJSONString(request);\n            jedis.hset(getItemKey(task), field, value);\n        }\n    }\n\n    private Request getExtrasInItem(Jedis jedis, String url, Task task) {\n        String key      = getItemKey(task);\n        String field    = DigestUtils.sha1Hex(url);\n        byte[] bytes    = jedis.hget(key.getBytes(), field.getBytes());\n        if (bytes != null) {\n            return JSON.parseObject(new String(bytes), Request.class);\n        }\n        return new Request(url);\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/scheduler/RedisScheduler.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.alibaba.fastjson.JSON;\n\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisPool;\nimport redis.clients.jedis.JedisPoolConfig;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.scheduler.component.DuplicateRemover;\n\n/**\n * Use Redis as url scheduler for distributed crawlers.<br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.0\n */\npublic class RedisScheduler extends DuplicateRemovedScheduler implements MonitorableScheduler, DuplicateRemover {\n\n    protected JedisPool pool;\n\n    private static final String QUEUE_PREFIX = \"queue_\";\n\n    private static final String SET_PREFIX = \"set_\";\n\n    private static final String ITEM_PREFIX = \"item_\";\n\n    public RedisScheduler(String host) {\n        this(new JedisPool(new JedisPoolConfig(), host));\n    }\n\n    public RedisScheduler(JedisPool pool) {\n        this.pool = pool;\n        setDuplicateRemover(this);\n    }\n\n    @Override\n    public void resetDuplicateCheck(Task task) {\n        try (Jedis jedis = pool.getResource()) {\n            jedis.del(getSetKey(task));\n        }\n    }\n\n    @Override\n    public boolean isDuplicate(Request request, Task task) {\n\t\ttry (Jedis jedis = pool.getResource()) {\n            return jedis.sadd(getSetKey(task), request.getUrl()) == 0;\n        }\n\n    }\n\n    @Override\n    protected void pushWhenNoDuplicate(Request request, Task task) {\n        Jedis jedis = pool.getResource();\n        try {\n            jedis.rpush(getQueueKey(task), request.getUrl());\n            if (checkForAdditionalInfo(request)) {\n                String field = DigestUtils.sha1Hex(request.getUrl());\n                String value = JSON.toJSONString(request);\n                jedis.hset((ITEM_PREFIX + task.getUUID()), field, value);\n            }\n        } finally {\n            jedis.close();\n        }\n    }\n\n    private boolean checkForAdditionalInfo(Request request) {\n        if (request == null) {\n            return false;\n        }\n\n        if (!request.getHeaders().isEmpty() || !request.getCookies().isEmpty()) {\n            return true;\n        }\n\n        if (StringUtils.isNotBlank(request.getCharset()) || StringUtils.isNotBlank(request.getMethod())) {\n            return true;\n        }\n\n        if (request.isBinaryContent() || request.getRequestBody() != null) {\n            return true;\n        }\n\n        if (!request.getExtras().isEmpty()) {\n            return true;\n        }\n        if (request.getPriority() != 0L) {\n            return true;\n        }\n\n        return false;\n    }\n\n    @Override\n    public synchronized Request poll(Task task) {\n\t\ttry (Jedis jedis = pool.getResource()) {\n            String url = jedis.lpop(getQueueKey(task));\n            if (url == null) {\n                return null;\n            }\n            String key = ITEM_PREFIX + task.getUUID();\n            String field = DigestUtils.sha1Hex(url);\n            byte[] bytes = jedis.hget(key.getBytes(), field.getBytes());\n            if (bytes != null) {\n                Request o = JSON.parseObject(new String(bytes), Request.class);\n                return o;\n            }\n            Request request = new Request(url);\n            return request;\n        }\n    }\n\n    protected String getSetKey(Task task) {\n        return SET_PREFIX + task.getUUID();\n    }\n\n    protected String getQueueKey(Task task) {\n        return QUEUE_PREFIX + task.getUUID();\n    }\n\n    protected String getItemKey(Task task) {\n        return ITEM_PREFIX + task.getUUID();\n    }\n\n    @Override\n    public int getLeftRequestsCount(Task task) {\n        try (Jedis jedis = pool.getResource()) {\n            Long size = jedis.llen(getQueueKey(task));\n            return size.intValue();\n        }\n    }\n\n    @Override\n    public int getTotalRequestsCount(Task task) {\n        try (Jedis jedis = pool.getResource()) {\n            Long size = jedis.scard(getSetKey(task));\n            return size.intValue();\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/utils/ClassUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport java.lang.reflect.Field;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.5.0\n */\npublic abstract class ClassUtils {\n\n    public static Set<Field> getFieldsIncludeSuperClass(Class clazz) {\n        Set<Field> fields = new LinkedHashSet<Field>();\n        Class current = clazz;\n        while (current != null) {\n            Field[] currentFields = current.getDeclaredFields();\n            for (Field currentField : currentFields) {\n                fields.add(currentField);\n            }\n            current = current.getSuperclass();\n        }\n        return fields;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/utils/DoubleKeyMap.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport java.util.Map;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class DoubleKeyMap<K1, K2, V> extends MultiKeyMapBase {\n    private Map<K1, Map<K2, V>> map;\n\n    public DoubleKeyMap() {\n        init();\n    }\n\n    public DoubleKeyMap(Map<K1, Map<K2, V>> map) {\n        this(map,DEFAULT_CLAZZ);\n    }\n\n    public DoubleKeyMap(Class<? extends Map> protoMapClass) {\n        super(protoMapClass);\n        init();\n    }\n\n    private void init() {\n        if (map == null) {\n            map = this.<K1, Map<K2, V>>newMap();\n        }\n    }\n\n    /**\n     * init map with protoMapClass\n     *\n     * @param map the origin map to contains the DoubleKeyMap\n     * @param protoMapClass protoMapClass\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public DoubleKeyMap(Map<K1, Map<K2, V>> map, Class<? extends Map> protoMapClass) {\n        super(protoMapClass);\n        this.map = map;\n        init();\n    }\n\n    /**\n     * @param key key\n     * @return map\n     */\n    public Map<K2, V> get(K1 key) {\n        return map.get(key);\n    }\n\n    /**\n     * @param key1 key1\n     * @param key2 key2\n     * @return value\n     */\n    public V get(K1 key1, K2 key2) {\n        if (get(key1) == null) {\n            return null;\n        }\n        return get(key1).get(key2);\n    }\n\n\n    /**\n     * @param key1 key1\n     * @param submap submap\n     * @return value\n     */\n    public V put(K1 key1, Map<K2, V> submap) {\n        return put(key1, submap);\n    }\n\n    /**\n     * @param key1 key1\n     * @param key2 key2\n     * @param value value\n     * @return value\n     */\n    public synchronized V put(K1 key1, K2 key2, V value) {\n        if (map.get(key1) == null) {\n            //不加锁的话，多个线程有可能都会执行到这里\n            map.put(key1, this.<K2, V>newMap());\n        }\n        return get(key1).put(key2, value);\n    }\n\n    /**\n     * @param key1 key1\n     * @param key2 key2\n     * @return value\n     */\n    public synchronized V remove(K1 key1, K2 key2) {\n        if (get(key1) == null) {\n            return null;\n        }\n        V remove = get(key1).remove(key2);\n        if (get(key1).size() == 0) {\n            remove(key1);\n        }\n        return remove;\n    }\n\n    /**\n     * @param key1 key1\n     * @return map\n     */\n    public Map<K2, V> remove(K1 key1) {\n        Map<K2, V> remove = map.remove(key1);\n        return remove;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/utils/ExtractorUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.selector.*;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Tools for annotation converting. <br>\n *\n * @author code4crafter@gmail.com <br>\n * @since 0.2.1\n */\npublic class ExtractorUtils {\n\n    public static Selector getSelector(ExtractBy extractBy) {\n        String value = extractBy.value();\n        Selector selector;\n        switch (extractBy.type()) {\n            case Css:\n                selector = new CssSelector(value);\n                break;\n            case Regex:\n                selector = new RegexSelector(value);\n                break;\n            case XPath:\n                selector = new XpathSelector(value);\n                break;\n            case JsonPath:\n                selector = new JsonPathSelector(value);\n                break;\n            default:\n                selector = new XpathSelector(value);\n        }\n        return selector;\n    }\n\n    public static List<Selector> getSelectors(ExtractBy[] extractBies) {\n        List<Selector> selectors = new ArrayList<Selector>();\n        if (extractBies == null) {\n            return selectors;\n        }\n        for (ExtractBy extractBy : extractBies) {\n            selectors.add(getSelector(extractBy));\n        }\n        return selectors;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/utils/IPUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.SocketException;\nimport java.util.Enumeration;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic abstract class IPUtils {\n\n    public static String getFirstNoLoopbackIPAddresses() throws SocketException {\n\n        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();\n\n        InetAddress localAddress = null;\n        while (networkInterfaces.hasMoreElements()) {\n            NetworkInterface networkInterface = networkInterfaces.nextElement();\n            Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();\n            while (inetAddresses.hasMoreElements()) {\n                InetAddress address = inetAddresses.nextElement();\n                if (!address.isLoopbackAddress() && !Inet6Address.class.isInstance(address)) {\n                    return address.getHostAddress();\n                } else if (!address.isLoopbackAddress()) {\n                    localAddress = address;\n                }\n            }\n        }\n\n        return localAddress.getHostAddress();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/utils/MultiKeyMapBase.java",
    "content": "package us.codecraft.webmagic.utils;\n\n/**\n * @author code4crafter@gmail.com\n */\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * multi-key map, some basic objects *\n *\n * @author yihua.huang\n */\npublic abstract class MultiKeyMapBase {\n\n    protected static final Class<? extends Map> DEFAULT_CLAZZ = HashMap.class;\n    @SuppressWarnings(\"rawtypes\")\n    private Class<? extends Map> protoMapClass = DEFAULT_CLAZZ;\n\n    public MultiKeyMapBase() {\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public MultiKeyMapBase(Class<? extends Map> protoMapClass) {\n        this.protoMapClass = protoMapClass;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    protected <K, V2> Map<K, V2> newMap() {\n        try {\n            return (Map<K, V2>) protoMapClass.newInstance();\n        } catch (InstantiationException e) {\n            throw new IllegalArgumentException(\"wrong proto type map \"\n                    + protoMapClass);\n        } catch (IllegalAccessException e) {\n            throw new IllegalArgumentException(\"wrong proto type map \"\n                    + protoMapClass);\n        }\n    }\n}"
  },
  {
    "path": "webmagic-extension/src/main/java/us/codecraft/webmagic/utils/RequestUtils.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport us.codecraft.webmagic.Request;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/6/5\n *         Time: 下午4:58\n */\npublic abstract class RequestUtils {\n\n    private static Pattern p4Range = Pattern.compile(\"\\\\[(\\\\d+)\\\\-(\\\\d+)\\\\]\");\n\n    public static List<Request> from(String exp){\n        Matcher matcher = p4Range.matcher(exp);\n        if (!matcher.find()) {\n            return Collections.singletonList(new Request(exp));\n        }\n        int rangeFrom = Integer.parseInt(matcher.group(1));\n        int rangeTo = Integer.parseInt(matcher.group(2));\n        if (rangeFrom > rangeTo) {\n            return Collections.emptyList();\n        }\n        List<Request> requests = new ArrayList<Request>(rangeTo - rangeFrom + 1);\n        for (int i = rangeFrom; i <= rangeTo; i++) {\n            requests.add(new Request(matcher.replaceAll(String.valueOf(i))));\n        }\n        return requests;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/main/resources/crawl.js",
    "content": "var system = require('system');\nvar url = system.args[1];\n\nvar page = require('webpage').create();\npage.settings.loadImages = false;\npage.settings.resourceTimeout = 5000;\n\npage.open(url, function (status) {\n    if (status != 'success') {\n        console.log(\"HTTP request failed!\");\n    } else {\n        console.log(page.content);\n    }\n\n    page.close();\n    phantom.exit();\n});"
  },
  {
    "path": "webmagic-extension/src/main/resources/spider-config-draft.xml",
    "content": "<!--This is a draft of config file.\nIf you have any advice, go https://github.com/code4craft/webmagic/issues/106 and comment!-->\n<spider>\n    <site>\n        <charset>utf-8</charset>\n        <user-agent></user-agent>\n        <cookies>\n            <cookie domain=\"\" path=\"\" name=\"\" value=\"\">\n            </cookie>\n        </cookies>\n        <heads>\n            <head name=\"\" value=\"\"/>\n        </heads>\n    </site>\n\n    <startUrls>\n        <url></url>\n    </startUrls>\n\n    <extraction targetUrl=\"\" helpUrl=\"\">\n        <field name=\"title\">\n            <extractor type=\"xpath\" value=\"//div[@class='title']\"/>\n        </field>\n        <field name=\"content\">\n            <extractor type=\"xpath\" value=\"//div[@class='content']\"/>\n        </field>\n    </extraction>\n\n</spider>"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/MockPageModelPipeline.java",
    "content": "package us.codecraft.webmagic;\n\nimport junit.framework.Assert;\nimport us.codecraft.webmagic.pipeline.PageModelPipeline;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class MockPageModelPipeline implements PageModelPipeline{\n    @Override\n    public void process(Object o, Task task) {\n        Assert.assertNotNull(o);\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/MockPipeline.java",
    "content": "package us.codecraft.webmagic;\n\nimport us.codecraft.webmagic.pipeline.Pipeline;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class MockPipeline implements Pipeline{\n    @Override\n    public void process(ResultItems resultItems, Task task) {\n\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/SimpleHttpClientTest.java",
    "content": "package us.codecraft.webmagic;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.model.AfterExtractor;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/6/3\n *         Time: 下午2:54\n */\npublic class SimpleHttpClientTest {\n\n    public static class Weather implements AfterExtractor {\n\n        private String location;\n\n        @ExtractBy(notNull = true, value = \"//div[@id='7d']//ul[@class='t']/li[2]/p[@class='tem']/i/regex('([\\\\-\\\\d]+)',1)\")\n        private Integer lowTemperature;\n\n        @ExtractBy(notNull = true, value = \"//div[@id='7d']//ul[@class='t']/li[2]/p[@class='tem']/span/regex('([\\\\-\\\\d]+)',1)\")\n        private Integer highTemperature;\n\n        @ExtractBy(notNull = true, value = \"//div[@id='7d']//ul[@class='t']/li[2]/p[@class='wea']/text()\")\n        private String desc;\n\n        @Override\n        public void afterProcess(Page page) {\n            if (lowTemperature > highTemperature) {\n                int temp = lowTemperature;\n                lowTemperature = highTemperature;\n                highTemperature = temp;\n            }\n        }\n\n        public String getLocation() {\n            return location;\n        }\n\n        public void setLocation(String location) {\n            this.location = location;\n        }\n\n        public Integer getLowTemperature() {\n            return lowTemperature;\n        }\n\n        public void setLowTemperature(Integer lowTemperature) {\n            this.lowTemperature = lowTemperature;\n        }\n\n        public Integer getHighTemperature() {\n            return highTemperature;\n        }\n\n        public void setHighTemperature(Integer highTemperature) {\n            this.highTemperature = highTemperature;\n        }\n\n        public String getDesc() {\n            return desc;\n        }\n\n        public void setDesc(String desc) {\n            this.desc = desc;\n        }\n\n        @Override\n        public String toString() {\n            return \"Weather{\" +\n                    \"location='\" + location + '\\'' +\n                    \", lowTemperature=\" + lowTemperature +\n                    \", highTemperature=\" + highTemperature +\n                    \", desc='\" + desc + '\\'' +\n                    '}';\n        }\n    }\n\n    @Ignore\n    @Test\n    public void test() throws Exception {\n        Weather weather = new SimpleHttpClient(Site.me()).get(\"http://www.weather.com.cn/weather/101020100.shtml\", Weather.class);\n        assertThat(weather).isNotNull();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/configurable/ConfigurablePageProcessorTest.java",
    "content": "package us.codecraft.webmagic.configurable;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.downloader.MockGithubDownloader;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class ConfigurablePageProcessorTest {\n\n    @Test\n    public void test() throws Exception {\n        List<ExtractRule> extractRules = new ArrayList<ExtractRule>();\n        ExtractRule extractRule = new ExtractRule();\n        extractRule.setExpressionType(ExpressionType.XPath);\n        extractRule.setExpressionValue(\"//title\");\n        extractRule.setFieldName(\"title\");\n        extractRules.add(extractRule);\n        extractRule = new ExtractRule();\n        extractRule.setExpressionType(ExpressionType.XPath);\n        extractRule.setExpressionValue(\"//ul[@class='pagehead-actions']/li[1]//a[@class='social-count js-social-count']/text()\");\n        extractRule.setFieldName(\"star\");\n        extractRules.add(extractRule);\n        ResultItems resultItems = Spider.create(new ConfigurablePageProcessor(Site.me(), extractRules))\n                .setDownloader(new MockGithubDownloader()).get(\"https://github.com/code4craft/webmagic\");\n        assertThat(resultItems.getAll()).containsEntry(\"title\", \"<title>code4craft/webmagic · GitHub</title>\");\n        assertThat(resultItems.getAll()).containsEntry(\"star\", \" 86 \");\n\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/downloader/MockGithubDownloader.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.selector.PlainText;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class MockGithubDownloader implements Downloader{\n\n    private String html = \"\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"<!DOCTYPE html>\\n\" +\n            \"<html>\\n\" +\n            \"  <head prefix=\\\"og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# githubog: http://ogp.me/ns/fb/githubog#\\\">\\n\" +\n            \"    <meta charset='utf-8'>\\n\" +\n            \"    <meta http-equiv=\\\"X-UA-Compatible\\\" content=\\\"IE=edge\\\">\\n\" +\n            \"        <title>code4craft/webmagic · GitHub</title>\\n\" +\n            \"    <link rel=\\\"search\\\" type=\\\"application/opensearchdescription+xml\\\" href=\\\"/opensearch.xml\\\" title=\\\"GitHub\\\" />\\n\" +\n            \"    <link rel=\\\"fluid-icon\\\" href=\\\"https://github.com/fluidicon.png\\\" title=\\\"GitHub\\\" />\\n\" +\n            \"    <link rel=\\\"apple-touch-icon\\\" sizes=\\\"57x57\\\" href=\\\"/apple-touch-icon-114.png\\\" />\\n\" +\n            \"    <link rel=\\\"apple-touch-icon\\\" sizes=\\\"114x114\\\" href=\\\"/apple-touch-icon-114.png\\\" />\\n\" +\n            \"    <link rel=\\\"apple-touch-icon\\\" sizes=\\\"72x72\\\" href=\\\"/apple-touch-icon-144.png\\\" />\\n\" +\n            \"    <link rel=\\\"apple-touch-icon\\\" sizes=\\\"144x144\\\" href=\\\"/apple-touch-icon-144.png\\\" />\\n\" +\n            \"    <link rel=\\\"logo\\\" type=\\\"image/svg\\\" href=\\\"https://github-media-downloads.s3.amazonaws.com/github-logo.svg\\\" />\\n\" +\n            \"    <meta property=\\\"og:image\\\" content=\\\"https://github.global.ssl.fastly.net/images/modules/logos_page/Octocat.png\\\">\\n\" +\n            \"    <meta name=\\\"hostname\\\" content=\\\"github-fe114-cp1-prd.iad.github.net\\\">\\n\" +\n            \"    <meta name=\\\"ruby\\\" content=\\\"ruby 1.9.3p194-tcs-github-tcmalloc (2012-05-25, TCS patched 2012-05-27, GitHub v1.0.36) [x86_64-linux]\\\">\\n\" +\n            \"    <link rel=\\\"assets\\\" href=\\\"https://github.global.ssl.fastly.net/\\\">\\n\" +\n            \"    <link rel=\\\"xhr-socket\\\" href=\\\"/_sockets\\\" />\\n\" +\n            \"    \\n\" +\n            \"    \\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"    <meta name=\\\"msapplication-TileImage\\\" content=\\\"/windows-tile.png\\\" />\\n\" +\n            \"    <meta name=\\\"msapplication-TileColor\\\" content=\\\"#ffffff\\\" />\\n\" +\n            \"    <meta name=\\\"selected-link\\\" value=\\\"repo_source\\\" data-pjax-transient />\\n\" +\n            \"    <meta content=\\\"collector.githubapp.com\\\" name=\\\"octolytics-host\\\" /><meta content=\\\"github\\\" name=\\\"octolytics-app-id\\\" /><meta content=\\\"D2167A02:4E87:89497A:523FCC67\\\" name=\\\"octolytics-dimension-request_id\\\" />\\n\" +\n            \"    \\n\" +\n            \"\\n\" +\n            \"    \\n\" +\n            \"    \\n\" +\n            \"    <link rel=\\\"icon\\\" type=\\\"image/x-icon\\\" href=\\\"/favicon.ico\\\" />\\n\" +\n            \"\\n\" +\n            \"    <meta content=\\\"authenticity_token\\\" name=\\\"csrf-param\\\" />\\n\" +\n            \"<meta content=\\\"i4/tXwrpqoMtPPKJTN4eSSPnFfrSzZkuIkeP//SUW34=\\\" name=\\\"csrf-token\\\" />\\n\" +\n            \"\\n\" +\n            \"    <link href=\\\"https://github.global.ssl.fastly.net/assets/github-4d622651f87d0cfd8c33f1c020455121d2af0be0.css\\\" media=\\\"all\\\" rel=\\\"stylesheet\\\" type=\\\"text/css\\\" />\\n\" +\n            \"    <link href=\\\"https://github.global.ssl.fastly.net/assets/github2-2c867c2081830b4a942703b9d3d565bf90f6046d.css\\\" media=\\\"all\\\" rel=\\\"stylesheet\\\" type=\\\"text/css\\\" />\\n\" +\n            \"    \\n\" +\n            \"\\n\" +\n            \"    \\n\" +\n            \"\\n\" +\n            \"      <script src=\\\"https://github.global.ssl.fastly.net/assets/frameworks-8db79d6d3d61c3bdec72ede901c2b6dbd4a79dad.js\\\" type=\\\"text/javascript\\\"></script>\\n\" +\n            \"      <script src=\\\"https://github.global.ssl.fastly.net/assets/github-0053cb56d6961482e50d72f8e19dc915009ce6b7.js\\\" type=\\\"text/javascript\\\"></script>\\n\" +\n            \"      \\n\" +\n            \"      <meta http-equiv=\\\"x-pjax-version\\\" content=\\\"b5479068af2118811ca4dcd8c0c29e66\\\">\\n\" +\n            \"\\n\" +\n            \"        <meta property=\\\"og:title\\\" content=\\\"webmagic\\\"/>\\n\" +\n            \"  <meta property=\\\"og:type\\\" content=\\\"githubog:gitrepository\\\"/>\\n\" +\n            \"  <meta property=\\\"og:url\\\" content=\\\"https://github.com/code4craft/webmagic\\\"/>\\n\" +\n            \"  <meta property=\\\"og:image\\\" content=\\\"https://github.global.ssl.fastly.net/images/gravatars/gravatar-user-420.png\\\"/>\\n\" +\n            \"  <meta property=\\\"og:site_name\\\" content=\\\"GitHub\\\"/>\\n\" +\n            \"  <meta property=\\\"og:description\\\" content=\\\"webmagic - A scalable web crawler framework.\\\"/>\\n\" +\n            \"\\n\" +\n            \"  <meta name=\\\"description\\\" content=\\\"webmagic - A scalable web crawler framework.\\\" />\\n\" +\n            \"\\n\" +\n            \"  <meta content=\\\"1351884\\\" name=\\\"octolytics-dimension-user_id\\\" /><meta content=\\\"code4craft\\\" name=\\\"octolytics-dimension-user_login\\\" /><meta content=\\\"9623064\\\" name=\\\"octolytics-dimension-repository_id\\\" /><meta content=\\\"code4craft/webmagic\\\" name=\\\"octolytics-dimension-repository_nwo\\\" /><meta content=\\\"true\\\" name=\\\"octolytics-dimension-repository_public\\\" /><meta content=\\\"false\\\" name=\\\"octolytics-dimension-repository_is_fork\\\" /><meta content=\\\"9623064\\\" name=\\\"octolytics-dimension-repository_network_root_id\\\" /><meta content=\\\"code4craft/webmagic\\\" name=\\\"octolytics-dimension-repository_network_root_nwo\\\" />\\n\" +\n            \"  <link href=\\\"https://github.com/code4craft/webmagic/commits/master.atom\\\" rel=\\\"alternate\\\" title=\\\"Recent Commits to webmagic:master\\\" type=\\\"application/atom+xml\\\" />\\n\" +\n            \"\\n\" +\n            \"  </head>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"  <body class=\\\"logged_out  env-production macintosh vis-public\\\">\\n\" +\n            \"    <div class=\\\"wrapper\\\">\\n\" +\n            \"      \\n\" +\n            \"      \\n\" +\n            \"      \\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"      \\n\" +\n            \"      <div class=\\\"header header-logged-out\\\">\\n\" +\n            \"  <div class=\\\"container clearfix\\\">\\n\" +\n            \"\\n\" +\n            \"    <a class=\\\"header-logo-wordmark\\\" href=\\\"https://github.com/\\\">\\n\" +\n            \"      <span class=\\\"mega-octicon octicon-logo-github\\\"></span>\\n\" +\n            \"    </a>\\n\" +\n            \"\\n\" +\n            \"    <div class=\\\"header-actions\\\">\\n\" +\n            \"        <a class=\\\"button primary\\\" href=\\\"/signup\\\">Sign up</a>\\n\" +\n            \"      <a class=\\\"button signin\\\" href=\\\"/login?return_to=%2Fcode4craft%2Fwebmagic\\\">Sign in</a>\\n\" +\n            \"    </div>\\n\" +\n            \"\\n\" +\n            \"    <div class=\\\"command-bar js-command-bar  in-repository\\\">\\n\" +\n            \"\\n\" +\n            \"      <ul class=\\\"top-nav\\\">\\n\" +\n            \"          <li class=\\\"explore\\\"><a href=\\\"/explore\\\">Explore</a></li>\\n\" +\n            \"        <li class=\\\"features\\\"><a href=\\\"/features\\\">Features</a></li>\\n\" +\n            \"          <li class=\\\"enterprise\\\"><a href=\\\"https://enterprise.github.com/\\\">Enterprise</a></li>\\n\" +\n            \"          <li class=\\\"blog\\\"><a href=\\\"/blog\\\">Blog</a></li>\\n\" +\n            \"      </ul>\\n\" +\n            \"        <form accept-charset=\\\"UTF-8\\\" action=\\\"/search\\\" class=\\\"command-bar-form\\\" id=\\\"top_search_form\\\" method=\\\"get\\\">\\n\" +\n            \"\\n\" +\n            \"<input type=\\\"text\\\" data-hotkey=\\\"/ s\\\" name=\\\"q\\\" id=\\\"js-command-bar-field\\\" placeholder=\\\"Search or type a command\\\" tabindex=\\\"1\\\" autocapitalize=\\\"off\\\"\\n\" +\n            \"    \\n\" +\n            \"    \\n\" +\n            \"      data-repo=\\\"code4craft/webmagic\\\"\\n\" +\n            \"      data-branch=\\\"master\\\"\\n\" +\n            \"      data-sha=\\\"c5ed5916d20b96963d906dde8bccc3627568e486\\\"\\n\" +\n            \"  >\\n\" +\n            \"\\n\" +\n            \"    <input type=\\\"hidden\\\" name=\\\"nwo\\\" value=\\\"code4craft/webmagic\\\" />\\n\" +\n            \"\\n\" +\n            \"    <div class=\\\"select-menu js-menu-container js-select-menu search-context-select-menu\\\">\\n\" +\n            \"      <span class=\\\"minibutton select-menu-button js-menu-target\\\">\\n\" +\n            \"        <span class=\\\"js-select-button\\\">This repository</span>\\n\" +\n            \"      </span>\\n\" +\n            \"\\n\" +\n            \"      <div class=\\\"select-menu-modal-holder js-menu-content js-navigation-container\\\">\\n\" +\n            \"        <div class=\\\"select-menu-modal\\\">\\n\" +\n            \"\\n\" +\n            \"          <div class=\\\"select-menu-item js-navigation-item js-this-repository-navigation-item selected\\\">\\n\" +\n            \"            <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"            <input type=\\\"radio\\\" class=\\\"js-search-this-repository\\\" name=\\\"search_target\\\" value=\\\"repository\\\" checked=\\\"checked\\\" />\\n\" +\n            \"            <div class=\\\"select-menu-item-text js-select-button-text\\\">This repository</div>\\n\" +\n            \"          </div> <!-- /.select-menu-item -->\\n\" +\n            \"\\n\" +\n            \"          <div class=\\\"select-menu-item js-navigation-item js-all-repositories-navigation-item\\\">\\n\" +\n            \"            <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"            <input type=\\\"radio\\\" name=\\\"search_target\\\" value=\\\"global\\\" />\\n\" +\n            \"            <div class=\\\"select-menu-item-text js-select-button-text\\\">All repositories</div>\\n\" +\n            \"          </div> <!-- /.select-menu-item -->\\n\" +\n            \"\\n\" +\n            \"        </div>\\n\" +\n            \"      </div>\\n\" +\n            \"    </div>\\n\" +\n            \"\\n\" +\n            \"  <span class=\\\"octicon help tooltipped downwards\\\" title=\\\"Show command bar help\\\">\\n\" +\n            \"    <span class=\\\"octicon octicon-question\\\"></span>\\n\" +\n            \"  </span>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"  <input type=\\\"hidden\\\" name=\\\"ref\\\" value=\\\"cmdform\\\">\\n\" +\n            \"\\n\" +\n            \"</form>\\n\" +\n            \"    </div>\\n\" +\n            \"\\n\" +\n            \"  </div>\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"      \\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"          <div class=\\\"site\\\" itemscope itemtype=\\\"http://schema.org/WebPage\\\">\\n\" +\n            \"    \\n\" +\n            \"    <div class=\\\"pagehead repohead instapaper_ignore readability-menu\\\">\\n\" +\n            \"      <div class=\\\"container\\\">\\n\" +\n            \"        \\n\" +\n            \"\\n\" +\n            \"<ul class=\\\"pagehead-actions\\\">\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"  <li>\\n\" +\n            \"  <a href=\\\"/login?return_to=%2Fcode4craft%2Fwebmagic\\\"\\n\" +\n            \"  class=\\\"minibutton with-count js-toggler-target star-button entice tooltipped upwards\\\"\\n\" +\n            \"  title=\\\"You must be signed in to use this feature\\\" rel=\\\"nofollow\\\">\\n\" +\n            \"  <span class=\\\"octicon octicon-star\\\"></span>Star\\n\" +\n            \"</a>\\n\" +\n            \"<a class=\\\"social-count js-social-count\\\" href=\\\"/code4craft/webmagic/stargazers\\\">\\n\" +\n            \"  86\\n\" +\n            \"</a>\\n\" +\n            \"\\n\" +\n            \"  </li>\\n\" +\n            \"\\n\" +\n            \"    <li>\\n\" +\n            \"      <a href=\\\"/login?return_to=%2Fcode4craft%2Fwebmagic\\\"\\n\" +\n            \"        class=\\\"minibutton with-count js-toggler-target fork-button entice tooltipped upwards\\\"\\n\" +\n            \"        title=\\\"You must be signed in to fork a repository\\\" rel=\\\"nofollow\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-git-branch\\\"></span>Fork\\n\" +\n            \"      </a>\\n\" +\n            \"      <a href=\\\"/code4craft/webmagic/network\\\" class=\\\"social-count\\\">\\n\" +\n            \"        70\\n\" +\n            \"      </a>\\n\" +\n            \"    </li>\\n\" +\n            \"</ul>\\n\" +\n            \"\\n\" +\n            \"        <h1 itemscope itemtype=\\\"http://data-vocabulary.org/Breadcrumb\\\" class=\\\"entry-title public\\\">\\n\" +\n            \"          <span class=\\\"repo-label\\\"><span>public</span></span>\\n\" +\n            \"          <span class=\\\"mega-octicon octicon-repo\\\"></span>\\n\" +\n            \"          <span class=\\\"author\\\">\\n\" +\n            \"            <a href=\\\"/code4craft\\\" class=\\\"url fn\\\" itemprop=\\\"url\\\" rel=\\\"author\\\"><span itemprop=\\\"title\\\">code4craft</span></a></span\\n\" +\n            \"          ><span class=\\\"repohead-name-divider\\\">/</span><strong\\n\" +\n            \"          ><a href=\\\"/code4craft/webmagic\\\" class=\\\"js-current-repository js-repo-home-link\\\">webmagic</a></strong>\\n\" +\n            \"\\n\" +\n            \"          <span class=\\\"page-context-loader\\\">\\n\" +\n            \"            <img alt=\\\"Octocat-spinner-32\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"          </span>\\n\" +\n            \"\\n\" +\n            \"        </h1>\\n\" +\n            \"      </div><!-- /.container -->\\n\" +\n            \"    </div><!-- /.repohead -->\\n\" +\n            \"\\n\" +\n            \"    <div class=\\\"container\\\">\\n\" +\n            \"\\n\" +\n            \"      <div class=\\\"repository-with-sidebar repo-container with-full-navigation\\\">\\n\" +\n            \"\\n\" +\n            \"        <div class=\\\"repository-sidebar\\\">\\n\" +\n            \"            \\n\" +\n            \"\\n\" +\n            \"<div class=\\\"repo-nav repo-nav-full js-repository-container-pjax js-octicon-loaders\\\">\\n\" +\n            \"  <div class=\\\"repo-nav-contents\\\">\\n\" +\n            \"    <ul class=\\\"repo-menu\\\">\\n\" +\n            \"      <li class=\\\"tooltipped leftwards\\\" title=\\\"Code\\\">\\n\" +\n            \"        <a href=\\\"/code4craft/webmagic\\\" aria-label=\\\"Code\\\" class=\\\"js-selected-navigation-item selected\\\" data-gotokey=\\\"c\\\" data-pjax=\\\"true\\\" data-selected-links=\\\"repo_source repo_downloads repo_commits repo_tags repo_branches /code4craft/webmagic\\\">\\n\" +\n            \"          <span class=\\\"octicon octicon-code\\\"></span> <span class=\\\"full-word\\\">Code</span>\\n\" +\n            \"          <img alt=\\\"Octocat-spinner-32\\\" class=\\\"mini-loader\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"</a>      </li>\\n\" +\n            \"\\n\" +\n            \"        <li class=\\\"tooltipped leftwards\\\" title=\\\"Issues\\\">\\n\" +\n            \"          <a href=\\\"/code4craft/webmagic/issues\\\" aria-label=\\\"Issues\\\" class=\\\"js-selected-navigation-item js-disable-pjax\\\" data-gotokey=\\\"i\\\" data-selected-links=\\\"repo_issues /code4craft/webmagic/issues\\\">\\n\" +\n            \"            <span class=\\\"octicon octicon-issue-opened\\\"></span> <span class=\\\"full-word\\\">Issues</span>\\n\" +\n            \"            <span class='counter'>2</span>\\n\" +\n            \"            <img alt=\\\"Octocat-spinner-32\\\" class=\\\"mini-loader\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"</a>        </li>\\n\" +\n            \"\\n\" +\n            \"      <li class=\\\"tooltipped leftwards\\\" title=\\\"Pull Requests\\\"><a href=\\\"/code4craft/webmagic/pulls\\\" aria-label=\\\"Pull Requests\\\" class=\\\"js-selected-navigation-item js-disable-pjax\\\" data-gotokey=\\\"p\\\" data-selected-links=\\\"repo_pulls /code4craft/webmagic/pulls\\\">\\n\" +\n            \"            <span class=\\\"octicon octicon-git-pull-request\\\"></span> <span class=\\\"full-word\\\">Pull Requests</span>\\n\" +\n            \"            <span class='counter'>0</span>\\n\" +\n            \"            <img alt=\\\"Octocat-spinner-32\\\" class=\\\"mini-loader\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"</a>      </li>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"        <li class=\\\"tooltipped leftwards\\\" title=\\\"Wiki\\\">\\n\" +\n            \"          <a href=\\\"/code4craft/webmagic/wiki\\\" aria-label=\\\"Wiki\\\" class=\\\"js-selected-navigation-item \\\" data-pjax=\\\"true\\\" data-selected-links=\\\"repo_wiki /code4craft/webmagic/wiki\\\">\\n\" +\n            \"            <span class=\\\"octicon octicon-book\\\"></span> <span class=\\\"full-word\\\">Wiki</span>\\n\" +\n            \"            <img alt=\\\"Octocat-spinner-32\\\" class=\\\"mini-loader\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"</a>        </li>\\n\" +\n            \"    </ul>\\n\" +\n            \"    <div class=\\\"repo-menu-separator\\\"></div>\\n\" +\n            \"    <ul class=\\\"repo-menu\\\">\\n\" +\n            \"\\n\" +\n            \"      <li class=\\\"tooltipped leftwards\\\" title=\\\"Pulse\\\">\\n\" +\n            \"        <a href=\\\"/code4craft/webmagic/pulse\\\" aria-label=\\\"Pulse\\\" class=\\\"js-selected-navigation-item \\\" data-pjax=\\\"true\\\" data-selected-links=\\\"pulse /code4craft/webmagic/pulse\\\">\\n\" +\n            \"          <span class=\\\"octicon octicon-pulse\\\"></span> <span class=\\\"full-word\\\">Pulse</span>\\n\" +\n            \"          <img alt=\\\"Octocat-spinner-32\\\" class=\\\"mini-loader\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"</a>      </li>\\n\" +\n            \"\\n\" +\n            \"      <li class=\\\"tooltipped leftwards\\\" title=\\\"Graphs\\\">\\n\" +\n            \"        <a href=\\\"/code4craft/webmagic/graphs\\\" aria-label=\\\"Graphs\\\" class=\\\"js-selected-navigation-item \\\" data-pjax=\\\"true\\\" data-selected-links=\\\"repo_graphs repo_contributors /code4craft/webmagic/graphs\\\">\\n\" +\n            \"          <span class=\\\"octicon octicon-graph\\\"></span> <span class=\\\"full-word\\\">Graphs</span>\\n\" +\n            \"          <img alt=\\\"Octocat-spinner-32\\\" class=\\\"mini-loader\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"</a>      </li>\\n\" +\n            \"\\n\" +\n            \"      <li class=\\\"tooltipped leftwards\\\" title=\\\"Network\\\">\\n\" +\n            \"        <a href=\\\"/code4craft/webmagic/network\\\" aria-label=\\\"Network\\\" class=\\\"js-selected-navigation-item js-disable-pjax\\\" data-selected-links=\\\"repo_network /code4craft/webmagic/network\\\">\\n\" +\n            \"          <span class=\\\"octicon octicon-git-branch\\\"></span> <span class=\\\"full-word\\\">Network</span>\\n\" +\n            \"          <img alt=\\\"Octocat-spinner-32\\\" class=\\\"mini-loader\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"</a>      </li>\\n\" +\n            \"    </ul>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"  </div>\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"            <div class=\\\"only-with-full-nav\\\">\\n\" +\n            \"              \\n\" +\n            \"\\n\" +\n            \"  \\n\" +\n            \"\\n\" +\n            \"<div class=\\\"clone-url open\\\"\\n\" +\n            \"  data-protocol-type=\\\"http\\\"\\n\" +\n            \"  data-url=\\\"/users/set_protocol?protocol_selector=http&amp;protocol_type=clone\\\">\\n\" +\n            \"  <h3><strong>HTTPS</strong> clone URL</h3>\\n\" +\n            \"  <div class=\\\"clone-url-box\\\">\\n\" +\n            \"    <input type=\\\"text\\\" class=\\\"clone js-url-field\\\"\\n\" +\n            \"           value=\\\"https://github.com/code4craft/webmagic.git\\\" readonly=\\\"readonly\\\">\\n\" +\n            \"\\n\" +\n            \"    <span class=\\\"js-zeroclipboard url-box-clippy minibutton zeroclipboard-button\\\" data-clipboard-text=\\\"https://github.com/code4craft/webmagic.git\\\" data-copied-hint=\\\"copied!\\\" title=\\\"copy to clipboard\\\"><span class=\\\"octicon octicon-clippy\\\"></span></span>\\n\" +\n            \"  </div>\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"  \\n\" +\n            \"\\n\" +\n            \"<div class=\\\"clone-url \\\"\\n\" +\n            \"  data-protocol-type=\\\"subversion\\\"\\n\" +\n            \"  data-url=\\\"/users/set_protocol?protocol_selector=subversion&amp;protocol_type=clone\\\">\\n\" +\n            \"  <h3><strong>Subversion</strong> checkout URL</h3>\\n\" +\n            \"  <div class=\\\"clone-url-box\\\">\\n\" +\n            \"    <input type=\\\"text\\\" class=\\\"clone js-url-field\\\"\\n\" +\n            \"           value=\\\"https://github.com/code4craft/webmagic\\\" readonly=\\\"readonly\\\">\\n\" +\n            \"\\n\" +\n            \"    <span class=\\\"js-zeroclipboard url-box-clippy minibutton zeroclipboard-button\\\" data-clipboard-text=\\\"https://github.com/code4craft/webmagic\\\" data-copied-hint=\\\"copied!\\\" title=\\\"copy to clipboard\\\"><span class=\\\"octicon octicon-clippy\\\"></span></span>\\n\" +\n            \"  </div>\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"<p class=\\\"clone-options\\\">You can clone with\\n\" +\n            \"      <a href=\\\"#\\\" class=\\\"js-clone-selector\\\" data-protocol=\\\"http\\\">HTTPS</a>,\\n\" +\n            \"      or <a href=\\\"#\\\" class=\\\"js-clone-selector\\\" data-protocol=\\\"subversion\\\">Subversion</a>.\\n\" +\n            \"  <span class=\\\"octicon help tooltipped upwards\\\" title=\\\"Get help on which URL is right for you.\\\">\\n\" +\n            \"    <a href=\\\"https://help.github.com/articles/which-remote-url-should-i-use\\\">\\n\" +\n            \"    <span class=\\\"octicon octicon-question\\\"></span>\\n\" +\n            \"    </a>\\n\" +\n            \"  </span>\\n\" +\n            \"</p>\\n\" +\n            \"\\n\" +\n            \"  <a href=\\\"http://mac.github.com\\\" class=\\\"minibutton sidebar-button\\\">\\n\" +\n            \"    <span class=\\\"octicon octicon-device-desktop\\\"></span>\\n\" +\n            \"    Clone in Desktop\\n\" +\n            \"  </a>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"                <a href=\\\"/code4craft/webmagic/archive/master.zip\\\"\\n\" +\n            \"                   class=\\\"minibutton sidebar-button\\\"\\n\" +\n            \"                   title=\\\"Download this repository as a zip file\\\"\\n\" +\n            \"                   rel=\\\"nofollow\\\">\\n\" +\n            \"                  <span class=\\\"octicon octicon-cloud-download\\\"></span>\\n\" +\n            \"                  Download ZIP\\n\" +\n            \"                </a>\\n\" +\n            \"            </div>\\n\" +\n            \"        </div><!-- /.repository-sidebar -->\\n\" +\n            \"\\n\" +\n            \"        <div id=\\\"js-repo-pjax-container\\\" class=\\\"repository-content context-loader-container\\\" data-pjax-container>\\n\" +\n            \"          \\n\" +\n            \"<div class=\\\"js-info-carrier\\\" data-show-full-navigation=\\\"yes\\\"></div>\\n\" +\n            \"\\n\" +\n            \"<div class=\\\"repository-meta js-details-container \\\">\\n\" +\n            \"    <div class=\\\"repository-description js-details-show\\\">\\n\" +\n            \"      <p>A scalable web crawler framework.</p>\\n\" +\n            \"    </div>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"<div class=\\\"capped-box overall-summary \\\">\\n\" +\n            \"\\n\" +\n            \"  <div class=\\\"stats-switcher-viewport js-stats-switcher-viewport\\\">\\n\" +\n            \"\\n\" +\n            \"    <ul class=\\\"numbers-summary\\\">\\n\" +\n            \"      <li class=\\\"commits\\\">\\n\" +\n            \"        <a data-pjax href=\\\"/code4craft/webmagic/commits/master\\\">\\n\" +\n            \"          <span class=\\\"num\\\">\\n\" +\n            \"            <span class=\\\"octicon octicon-history\\\"></span>\\n\" +\n            \"            311\\n\" +\n            \"          </span>\\n\" +\n            \"          commits\\n\" +\n            \"        </a>\\n\" +\n            \"      </li>\\n\" +\n            \"      <li>\\n\" +\n            \"        <a data-pjax href=\\\"/code4craft/webmagic/branches\\\">\\n\" +\n            \"          <span class=\\\"num\\\">\\n\" +\n            \"            <span class=\\\"octicon octicon-git-branch\\\"></span>\\n\" +\n            \"            4\\n\" +\n            \"          </span>\\n\" +\n            \"          branches\\n\" +\n            \"        </a>\\n\" +\n            \"      </li>\\n\" +\n            \"\\n\" +\n            \"      <li>\\n\" +\n            \"        <a data-pjax href=\\\"/code4craft/webmagic/releases\\\">\\n\" +\n            \"          <span class=\\\"num\\\">\\n\" +\n            \"            <span class=\\\"octicon octicon-tag\\\"></span>\\n\" +\n            \"            5\\n\" +\n            \"          </span>\\n\" +\n            \"          releases\\n\" +\n            \"        </a>\\n\" +\n            \"      </li>\\n\" +\n            \"\\n\" +\n            \"      <li>\\n\" +\n            \"        <a href=\\\"/code4craft/webmagic/contributors\\\">\\n\" +\n            \"          <span class=\\\"num\\\">\\n\" +\n            \"            <span class=\\\"octicon octicon-organization\\\"></span>\\n\" +\n            \"            3\\n\" +\n            \"          </span>\\n\" +\n            \"          contributors\\n\" +\n            \"        </a>\\n\" +\n            \"      </li>\\n\" +\n            \"    </ul>\\n\" +\n            \"\\n\" +\n            \"      <div class=\\\"repository-lang-stats\\\">\\n\" +\n            \"        <ol class=\\\"repository-lang-stats-numbers\\\">\\n\" +\n            \"          <li>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/search?l=java\\\">\\n\" +\n            \"                <span class=\\\"color-block language-color\\\" style=\\\"background-color:#b07219;\\\"></span>\\n\" +\n            \"                <span class=\\\"lang\\\">Java</span>\\n\" +\n            \"                <span class=\\\"percent\\\">100%</span>\\n\" +\n            \"              </a>\\n\" +\n            \"          </li>\\n\" +\n            \"        </ol>\\n\" +\n            \"      </div>\\n\" +\n            \"  </div>\\n\" +\n            \"\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"  <a href=\\\"#\\\"\\n\" +\n            \"     class=\\\"repository-lang-stats-graph js-toggle-lang-stats tooltipped downwards\\\"\\n\" +\n            \"     title=\\\"Show language statistics\\\"\\n\" +\n            \"     style=\\\"background-color:#b07219\\\">\\n\" +\n            \"  <span class=\\\"language-color\\\" style=\\\"width:100%; background-color:#b07219;\\\" itemprop=\\\"keywords\\\">Java</span>\\n\" +\n            \"  </a>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"<div class=\\\"file-navigation in-mid-page\\\">\\n\" +\n            \"    <a href=\\\"/code4craft/webmagic/compare\\\" aria-label=\\\"Compare, review, create a pull request\\\" class=\\\"minibutton compact primary tooltipped downwards\\\" title=\\\"Compare &amp; review\\\" data-pjax>\\n\" +\n            \"      <span class=\\\"octicon octicon-git-compare\\\"></span>\\n\" +\n            \"    </a>\\n\" +\n            \"\\n\" +\n            \"  \\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"<div class=\\\"select-menu js-menu-container js-select-menu\\\" >\\n\" +\n            \"  <span class=\\\"minibutton select-menu-button js-menu-target\\\" data-hotkey=\\\"w\\\"\\n\" +\n            \"    data-master-branch=\\\"master\\\"\\n\" +\n            \"    data-ref=\\\"master\\\"\\n\" +\n            \"    role=\\\"button\\\" aria-label=\\\"Switch branches or tags\\\" tabindex=\\\"0\\\">\\n\" +\n            \"    <span class=\\\"octicon octicon-git-branch\\\"></span>\\n\" +\n            \"    <i>branch:</i>\\n\" +\n            \"    <span class=\\\"js-select-button\\\">master</span>\\n\" +\n            \"  </span>\\n\" +\n            \"\\n\" +\n            \"  <div class=\\\"select-menu-modal-holder js-menu-content js-navigation-container\\\" data-pjax>\\n\" +\n            \"\\n\" +\n            \"    <div class=\\\"select-menu-modal\\\">\\n\" +\n            \"      <div class=\\\"select-menu-header\\\">\\n\" +\n            \"        <span class=\\\"select-menu-title\\\">Switch branches/tags</span>\\n\" +\n            \"        <span class=\\\"octicon octicon-remove-close js-menu-close\\\"></span>\\n\" +\n            \"      </div> <!-- /.select-menu-header -->\\n\" +\n            \"\\n\" +\n            \"      <div class=\\\"select-menu-filters\\\">\\n\" +\n            \"        <div class=\\\"select-menu-text-filter\\\">\\n\" +\n            \"          <input type=\\\"text\\\" aria-label=\\\"Filter branches/tags\\\" id=\\\"context-commitish-filter-field\\\" class=\\\"js-filterable-field js-navigation-enable\\\" placeholder=\\\"Filter branches/tags\\\">\\n\" +\n            \"        </div>\\n\" +\n            \"        <div class=\\\"select-menu-tabs\\\">\\n\" +\n            \"          <ul>\\n\" +\n            \"            <li class=\\\"select-menu-tab\\\">\\n\" +\n            \"              <a href=\\\"#\\\" data-tab-filter=\\\"branches\\\" class=\\\"js-select-menu-tab\\\">Branches</a>\\n\" +\n            \"            </li>\\n\" +\n            \"            <li class=\\\"select-menu-tab\\\">\\n\" +\n            \"              <a href=\\\"#\\\" data-tab-filter=\\\"tags\\\" class=\\\"js-select-menu-tab\\\">Tags</a>\\n\" +\n            \"            </li>\\n\" +\n            \"          </ul>\\n\" +\n            \"        </div><!-- /.select-menu-tabs -->\\n\" +\n            \"      </div><!-- /.select-menu-filters -->\\n\" +\n            \"\\n\" +\n            \"      <div class=\\\"select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket\\\" data-tab-filter=\\\"branches\\\">\\n\" +\n            \"\\n\" +\n            \"        <div data-filterable-for=\\\"context-commitish-filter-field\\\" data-filterable-type=\\\"substring\\\">\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item \\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/en-webmagic\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"en-webmagic\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"en-webmagic\\\">en-webmagic</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item \\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/gh-pages\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"gh-pages\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"gh-pages\\\">gh-pages</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item selected\\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/master\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"master\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"master\\\">master</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item \\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/xsoup\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"xsoup\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"xsoup\\\">xsoup</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"        </div>\\n\" +\n            \"\\n\" +\n            \"          <div class=\\\"select-menu-no-results\\\">Nothing to show</div>\\n\" +\n            \"      </div> <!-- /.select-menu-list -->\\n\" +\n            \"\\n\" +\n            \"      <div class=\\\"select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket\\\" data-tab-filter=\\\"tags\\\">\\n\" +\n            \"        <div data-filterable-for=\\\"context-commitish-filter-field\\\" data-filterable-type=\\\"substring\\\">\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item \\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/webmagic-parent-0.3.1\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"webmagic-parent-0.3.1\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"webmagic-parent-0.3.1\\\">webmagic-parent-0.3.1</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item \\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/webmagic-parent-0.2.1\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"webmagic-parent-0.2.1\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"webmagic-parent-0.2.1\\\">webmagic-parent-0.2.1</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item \\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/webmagic-0.3.0\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"webmagic-0.3.0\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"webmagic-0.3.0\\\">webmagic-0.3.0</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item \\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/version-0.2.0\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"version-0.2.0\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"version-0.2.0\\\">version-0.2.0</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"            <div class=\\\"select-menu-item js-navigation-item \\\">\\n\" +\n            \"              <span class=\\\"select-menu-item-icon octicon octicon-check\\\"></span>\\n\" +\n            \"              <a href=\\\"/code4craft/webmagic/tree/version-0.1.0\\\" class=\\\"js-navigation-open select-menu-item-text js-select-button-text css-truncate-target\\\" data-name=\\\"version-0.1.0\\\" data-skip-pjax=\\\"true\\\" rel=\\\"nofollow\\\" title=\\\"version-0.1.0\\\">version-0.1.0</a>\\n\" +\n            \"            </div> <!-- /.select-menu-item -->\\n\" +\n            \"        </div>\\n\" +\n            \"\\n\" +\n            \"        <div class=\\\"select-menu-no-results\\\">Nothing to show</div>\\n\" +\n            \"      </div> <!-- /.select-menu-list -->\\n\" +\n            \"\\n\" +\n            \"    </div> <!-- /.select-menu-modal -->\\n\" +\n            \"  </div> <!-- /.select-menu-modal-holder -->\\n\" +\n            \"</div> <!-- /.select-menu -->\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"  <div class=\\\"breadcrumb\\\"><span class='repo-root js-repo-root'><span itemscope=\\\"\\\" itemtype=\\\"http://data-vocabulary.org/Breadcrumb\\\"><a href=\\\"/code4craft/webmagic\\\" data-branch=\\\"master\\\" data-direction=\\\"back\\\" data-pjax=\\\"true\\\" itemscope=\\\"url\\\"><span itemprop=\\\"title\\\">webmagic</span></a></span></span><span class=\\\"separator\\\"> / </span><form action=\\\"/login?return_to=%2Fcode4craft%2Fwebmagic\\\" class=\\\"js-new-blob-form tooltipped rightwards new-file-link\\\" method=\\\"post\\\" title=\\\"Sign in to make or propose changes\\\"><span aria-label=\\\"Sign in to make or propose changes\\\" class=\\\"js-new-blob-submit octicon octicon-file-add\\\" data-test-id=\\\"create-new-git-file\\\" role=\\\"button\\\"></span></form></div>\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"<a href=\\\"/code4craft/webmagic/find/master\\\"\\n\" +\n            \"  data-hotkey=\\\"t\\\" class=\\\"js-show-file-finder\\\" style=\\\"display:none\\\" data-pjax>Show File Finder</a>\\n\" +\n            \"<div class=\\\"bubble files-bubble\\\">\\n\" +\n            \"  <table class=\\\"files\\\" data-pjax>\\n\" +\n            \"    <thead>\\n\" +\n            \"\\n\" +\n            \"        <div class=\\\"commit commit-loader commit-tease js-details-container js-deferred-content\\\" data-url=\\\"/code4craft/webmagic/tree-commit/master\\\">\\n\" +\n            \"          <p class=\\\"commit-title blank\\\">\\n\" +\n            \"            Fetching latest commit…\\n\" +\n            \"          </p>\\n\" +\n            \"          <div class=\\\"commit-meta\\\">\\n\" +\n            \"            <p class=\\\"loader-loading\\\"><img alt=\\\"Octocat-spinner-32-eaf2f5\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32-EAF2F5.gif\\\" width=\\\"16\\\" /></p>\\n\" +\n            \"            <p class=\\\"loader-error\\\">Cannot retrieve the latest commit at this time</p>\\n\" +\n            \"          </div>\\n\" +\n            \"        </div>\\n\" +\n            \"    </thead>\\n\" +\n            \"\\n\" +\n            \"    \\n\" +\n            \"<tbody class=\\\"\\\"\\n\" +\n            \"  data-url=\\\"/code4craft/webmagic/file-list/master\\\">\\n\" +\n            \"    <tr class=\\\"alt\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-directory\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/tree/master/en_docs\\\" class=\\\"js-directory-link\\\" id=\\\"025516923597c2d7f987828ad6657c14-6f7a9bdb73f0e5e26cbde50c2fbf780c2a4ad4b2\\\" title=\\\"en_docs\\\">en_docs</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/dcc5d790e4bcbebaa2a1168fd5f9919936bcb831\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"update readme\\\">update readme</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-08-17T16:16:33-07:00\\\" title=\\\"2013-08-17 16:16:33\\\">August 17, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-directory\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/tree/master/webmagic-core\\\" class=\\\"js-directory-link\\\" id=\\\"39809e13bc65c3873f79570b81852d62-e96da9edd9329cf8448fed332294dd4575549495\\\" title=\\\"webmagic-core\\\">webmagic-core</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/b131878123cb90f6123255bbd21e71bc70a480b7\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"add example\\\">add example</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-22T22:01:28-07:00\\\" title=\\\"2013-09-22 22:01:28\\\">September 22, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"alt\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-directory\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/tree/master/webmagic-extension\\\" class=\\\"js-directory-link\\\" id=\\\"dc82c79bcb262e1942088502bb426876-6f4453065d5b11429731e2a3e71e10f944da2180\\\" title=\\\"webmagic-extension\\\">webmagic-extension</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/b131878123cb90f6123255bbd21e71bc70a480b7\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"add example\\\">add example</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-22T22:01:28-07:00\\\" title=\\\"2013-09-22 22:01:28\\\">September 22, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-directory\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/tree/master/webmagic-lucene\\\" class=\\\"js-directory-link\\\" id=\\\"e686efe9e2cd770dcf86d93b9ddb2036-e16df360eb86bf0c21be610105981182a5e2ac05\\\" title=\\\"webmagic-lucene\\\">webmagic-lucene</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/7003426898ec684194a67130914c17c1566ed233\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"update pom\\\">update pom</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-08-20T06:52:39-07:00\\\" title=\\\"2013-08-20 06:52:39\\\">August 20, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"alt\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-directory\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/tree/master/webmagic-samples\\\" class=\\\"js-directory-link\\\" id=\\\"4284b70d4c5e11003fb292b0d0f7539f-55f538835cd8b15fb4e34c8a0d6491dc9559e610\\\" title=\\\"webmagic-samples\\\">webmagic-samples</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/95ab4edec3daca3353395909a13085079ff8606b\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"some bugfix\\\">some bugfix</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-22T17:38:54-07:00\\\" title=\\\"2013-09-22 17:38:54\\\">September 22, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-directory\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/tree/master/webmagic-saxon\\\" class=\\\"js-directory-link\\\" id=\\\"5ee0de5b970664e15f6805d957403c63-c498acdbb391d3ae9ee0088ff086312c11aad18d\\\" title=\\\"webmagic-saxon\\\">webmagic-saxon</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/b1cba78bd6930bbbc3d44b4825fcc752932ca02c\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"xsoup test\\\">xsoup test</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-01T16:30:31-07:00\\\" title=\\\"2013-09-01 16:30:31\\\">September 01, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"alt\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-directory\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/tree/master/webmagic-selenium\\\" class=\\\"js-directory-link\\\" id=\\\"988c197af393f3198711cebacce7fd65-210d6b3ddaf0bc962553f1244495b6960fbd8994\\\" title=\\\"webmagic-selenium\\\">webmagic-selenium</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/7003426898ec684194a67130914c17c1566ed233\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"update pom\\\">update pom</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-08-20T06:52:39-07:00\\\" title=\\\"2013-08-20 06:52:39\\\">August 20, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-directory\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/tree/master/zh_docs\\\" class=\\\"js-directory-link\\\" id=\\\"bec3b859688b0bbdb94899b1a5b56441-2cf0c7c178e3e0280b023f54e3ef21e9b7b9e3b3\\\" title=\\\"zh_docs\\\">zh_docs</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/81f75347573f70a39a83afd5d2f7d626b3b305bd\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"update version\\\">update version</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-12T06:28:42-07:00\\\" title=\\\"2013-09-12 06:28:42\\\">September 12, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"alt\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-text\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/blob/master/.gitignore\\\" class=\\\"js-directory-link\\\" id=\\\"a084b794bc0759e7a6b77810e01874f2-8e88e25dbf702e915d3d4839cbbca007859874b2\\\" title=\\\".gitignore\\\">.gitignore</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/4d023b3666cc2101e92540004e5630dd2aa01319\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"增加剔除文件\\\">增加剔除文件</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-04T09:30:52-07:00\\\" title=\\\"2013-09-04 09:30:52\\\">September 04, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-text\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/blob/master/.travis.yml\\\" class=\\\"js-directory-link\\\" id=\\\"354f30a63fb0907d4ad57269548329e3-c7c99f406eb2b126614aacf99fb9e103ce30ce00\\\" title=\\\".travis.yml\\\">.travis.yml</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/95c8b2a8a44262fc20c4d5eddb9cf7ba18cfb753\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"add jdk\\\">add jdk</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-06-20T02:54:46-07:00\\\" title=\\\"2013-06-20 02:54:46\\\">June 20, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"alt\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-text\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/blob/master/README.md\\\" class=\\\"js-directory-link\\\" id=\\\"04c6e90faac2675aa89e2176d2eec7d8-01a868db17802ce7915cc2bcfad10244ef4de064\\\" title=\\\"README.md\\\">README.md</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/a0d64b76357a449386755b9867163c91d04a2426\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"update version\\\">update version</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-12T06:06:05-07:00\\\" title=\\\"2013-09-12 06:06:05\\\">September 12, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-text\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/blob/master/pom.xml\\\" class=\\\"js-directory-link\\\" id=\\\"600376dffeb79835ede4a0b285078036-e2685a8ad6dbce1421232fced6e46ed3c8c3efa2\\\" title=\\\"pom.xml\\\">pom.xml</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/fb693a4ac41667ba70f2d7c11c73b364fa569e67\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"[maven-release-plugin] prepare for next development iteration\\\">[maven-release-plugin] prepare for next development iteration</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-08T07:25:07-07:00\\\" title=\\\"2013-09-08 07:25:07\\\">September 08, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"alt\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-text\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/blob/master/release-note.md\\\" class=\\\"js-directory-link\\\" id=\\\"d59c2d5d8d04d144da5f1cd251c384ad-001568be91dd7d90d2d26c06c192725af5ddd25e\\\" title=\\\"release-note.md\\\">release-note.md</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/a9fc06a916008e9763dec67d240e84d81e94185d\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"release note\\\">release note</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-09-03T20:04:36-07:00\\\" title=\\\"2013-09-03 20:04:36\\\">September 03, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"    <tr class=\\\"\\\">\\n\" +\n            \"      <td class=\\\"icon\\\">\\n\" +\n            \"        <span class=\\\"octicon octicon-file-text\\\"></span>\\n\" +\n            \"        <img alt=\\\"Octocat-spinner-32\\\" class=\\\"spinner\\\" height=\\\"16\\\" src=\\\"https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif\\\" width=\\\"16\\\" />\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"content\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/blob/master/webmagic%20manual.md\\\" class=\\\"js-directory-link\\\" id=\\\"7a8cd261f7c7be5bd05d8f2ce23a818c-dc09b907d8873ba1c34c38b8cea2062af53ef625\\\" title=\\\"webmagic manual.md\\\">webmagic manual.md</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"message\\\">\\n\" +\n            \"        <span class=\\\"css-truncate css-truncate-target\\\"><a href=\\\"/code4craft/webmagic/commit/4fa82aad20b0d208c8c2b17af2644f82b26c1b75\\\" class=\\\"message\\\" data-pjax=\\\"true\\\" title=\\\"readme\\\">readme</a></span>\\n\" +\n            \"      </td>\\n\" +\n            \"      <td class=\\\"age\\\"><span class=\\\"css-truncate css-truncate-target\\\"><time class=\\\"js-relative-date\\\" datetime=\\\"2013-08-20T16:44:39-07:00\\\" title=\\\"2013-08-20 16:44:39\\\">August 20, 2013</time></span></td>\\n\" +\n            \"    </tr>\\n\" +\n            \"</tbody>\\n\" +\n            \"\\n\" +\n            \"  </table>\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"  <div id=\\\"readme\\\" class=\\\"clearfix announce instapaper_body md\\\">\\n\" +\n            \"    <span class=\\\"name\\\"><span class=\\\"octicon octicon-book\\\"></span> README.md</span><article class=\\\"markdown-body entry-content\\\" itemprop=\\\"mainContentOfPage\\\"><h2>\\n\" +\n            \"<a name=\\\"webmagic\\\" class=\\\"anchor\\\" href=\\\"#webmagic\\\"><span class=\\\"octicon octicon-link\\\"></span></a>webmagic</h2>\\n\" +\n            \"\\n\" +\n            \"<p><a href=\\\"https://github.com/code4craft/webmagic/tree/master/zh_docs\\\">Readme in Chinese</a></p>\\n\" +\n            \"\\n\" +\n            \"<p><a href=\\\"https://travis-ci.org/code4craft/webmagic\\\"><img src=\\\"https://travis-ci.org/code4craft/webmagic.png?branch=master\\\" alt=\\\"Build Status\\\" style=\\\"max-width:100%;\\\"></a></p>\\n\" +\n            \"\\n\" +\n            \"<blockquote>\\n\" +\n            \"<p>A scalable crawler framework. It covers the whole lifecycle of crawler: downloading, url management, content extraction and persistent. It can simplify the development of a  specific crawler.</p>\\n\" +\n            \"</blockquote>\\n\" +\n            \"\\n\" +\n            \"<h2>\\n\" +\n            \"<a name=\\\"features\\\" class=\\\"anchor\\\" href=\\\"#features\\\"><span class=\\\"octicon octicon-link\\\"></span></a>Features:</h2>\\n\" +\n            \"\\n\" +\n            \"<ul>\\n\" +\n            \"<li>Simple core with high flexibility.</li>\\n\" +\n            \"<li>Simple API for html extracting.</li>\\n\" +\n            \"<li>Annotation with POJO to customize a crawler, no configuration.</li>\\n\" +\n            \"<li>Multi-thread and Distribution support.</li>\\n\" +\n            \"<li>Easy to be integrated.</li>\\n\" +\n            \"</ul><h2>\\n\" +\n            \"<a name=\\\"install\\\" class=\\\"anchor\\\" href=\\\"#install\\\"><span class=\\\"octicon octicon-link\\\"></span></a>Install:</h2>\\n\" +\n            \"\\n\" +\n            \"<p>Add dependencies to your pom.xml:</p>\\n\" +\n            \"\\n\" +\n            \"<pre><code>    &lt;dependency&gt;\\n\" +\n            \"        &lt;groupId&gt;us.codecraft&lt;/groupId&gt;\\n\" +\n            \"        &lt;artifactId&gt;webmagic-core&lt;/artifactId&gt;\\n\" +\n            \"        &lt;version&gt;0.3.1&lt;/version&gt;\\n\" +\n            \"    &lt;/dependency&gt;\\n\" +\n            \"    &lt;dependency&gt;\\n\" +\n            \"        &lt;groupId&gt;us.codecraft&lt;/groupId&gt;\\n\" +\n            \"        &lt;artifactId&gt;webmagic-extension&lt;/artifactId&gt;\\n\" +\n            \"        &lt;version&gt;0.3.1&lt;/version&gt;\\n\" +\n            \"    &lt;/dependency&gt;\\n\" +\n            \"</code></pre>\\n\" +\n            \"\\n\" +\n            \"<h2>\\n\" +\n            \"<a name=\\\"get-started\\\" class=\\\"anchor\\\" href=\\\"#get-started\\\"><span class=\\\"octicon octicon-link\\\"></span></a>Get Started:</h2>\\n\" +\n            \"\\n\" +\n            \"<h3>\\n\" +\n            \"<a name=\\\"first-crawler\\\" class=\\\"anchor\\\" href=\\\"#first-crawler\\\"><span class=\\\"octicon octicon-link\\\"></span></a>First crawler:</h3>\\n\" +\n            \"\\n\" +\n            \"<p>Write a class implements PageProcessor：</p>\\n\" +\n            \"\\n\" +\n            \"<div class=\\\"highlight highlight-java\\\"><pre>    <span class=\\\"kd\\\">public</span> <span class=\\\"kd\\\">class</span> <span class=\\\"nc\\\">OschinaBlogPageProcessor</span> <span class=\\\"kd\\\">implements</span> <span class=\\\"n\\\">PageProcessor</span> <span class=\\\"o\\\">{</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"kd\\\">private</span> <span class=\\\"n\\\">Site</span> <span class=\\\"n\\\">site</span> <span class=\\\"o\\\">=</span> <span class=\\\"n\\\">Site</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">me</span><span class=\\\"o\\\">().</span><span class=\\\"na\\\">setDomain</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"my.oschina.net\\\"</span><span class=\\\"o\\\">)</span>\\n\" +\n            \"           <span class=\\\"o\\\">.</span><span class=\\\"na\\\">addStartUrl</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"http://my.oschina.net/flashsword/blog\\\"</span><span class=\\\"o\\\">);</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"nd\\\">@Override</span>\\n\" +\n            \"        <span class=\\\"kd\\\">public</span> <span class=\\\"kt\\\">void</span> <span class=\\\"nf\\\">process</span><span class=\\\"o\\\">(</span><span class=\\\"n\\\">Page</span> <span class=\\\"n\\\">page</span><span class=\\\"o\\\">)</span> <span class=\\\"o\\\">{</span>\\n\" +\n            \"            <span class=\\\"n\\\">List</span><span class=\\\"o\\\">&lt;</span><span class=\\\"n\\\">String</span><span class=\\\"o\\\">&gt;</span> <span class=\\\"n\\\">links</span> <span class=\\\"o\\\">=</span> <span class=\\\"n\\\">page</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">getHtml</span><span class=\\\"o\\\">().</span><span class=\\\"na\\\">links</span><span class=\\\"o\\\">().</span><span class=\\\"na\\\">regex</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"http://my\\\\\\\\.oschina\\\\\\\\.net/flashsword/blog/\\\\\\\\d+\\\"</span><span class=\\\"o\\\">).</span><span class=\\\"na\\\">all</span><span class=\\\"o\\\">();</span>\\n\" +\n            \"            <span class=\\\"n\\\">page</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">addTargetRequests</span><span class=\\\"o\\\">(</span><span class=\\\"n\\\">links</span><span class=\\\"o\\\">);</span>\\n\" +\n            \"            <span class=\\\"n\\\">page</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">putField</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"title\\\"</span><span class=\\\"o\\\">,</span> <span class=\\\"n\\\">page</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">getHtml</span><span class=\\\"o\\\">().</span><span class=\\\"na\\\">xpath</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"//div[@class='BlogEntity']/div[@class='BlogTitle']/h1\\\"</span><span class=\\\"o\\\">).</span><span class=\\\"na\\\">toString</span><span class=\\\"o\\\">());</span>\\n\" +\n            \"            <span class=\\\"n\\\">page</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">putField</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"content\\\"</span><span class=\\\"o\\\">,</span> <span class=\\\"n\\\">page</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">getHtml</span><span class=\\\"o\\\">().</span><span class=\\\"n\\\">$</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"div.content\\\"</span><span class=\\\"o\\\">).</span><span class=\\\"na\\\">toString</span><span class=\\\"o\\\">());</span>\\n\" +\n            \"            <span class=\\\"n\\\">page</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">putField</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"tags\\\"</span><span class=\\\"o\\\">,</span><span class=\\\"n\\\">page</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">getHtml</span><span class=\\\"o\\\">().</span><span class=\\\"na\\\">xpath</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"//div[@class='BlogTags']/a/text()\\\"</span><span class=\\\"o\\\">).</span><span class=\\\"na\\\">all</span><span class=\\\"o\\\">());</span>\\n\" +\n            \"        <span class=\\\"o\\\">}</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"nd\\\">@Override</span>\\n\" +\n            \"        <span class=\\\"kd\\\">public</span> <span class=\\\"n\\\">Site</span> <span class=\\\"nf\\\">getSite</span><span class=\\\"o\\\">()</span> <span class=\\\"o\\\">{</span>\\n\" +\n            \"            <span class=\\\"k\\\">return</span> <span class=\\\"n\\\">site</span><span class=\\\"o\\\">;</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"o\\\">}</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"kd\\\">public</span> <span class=\\\"kd\\\">static</span> <span class=\\\"kt\\\">void</span> <span class=\\\"nf\\\">main</span><span class=\\\"o\\\">(</span><span class=\\\"n\\\">String</span><span class=\\\"o\\\">[]</span> <span class=\\\"n\\\">args</span><span class=\\\"o\\\">)</span> <span class=\\\"o\\\">{</span>\\n\" +\n            \"            <span class=\\\"n\\\">Spider</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">create</span><span class=\\\"o\\\">(</span><span class=\\\"k\\\">new</span> <span class=\\\"n\\\">OschinaBlogPageProcessor</span><span class=\\\"o\\\">())</span>\\n\" +\n            \"                 <span class=\\\"o\\\">.</span><span class=\\\"na\\\">pipeline</span><span class=\\\"o\\\">(</span><span class=\\\"k\\\">new</span> <span class=\\\"n\\\">ConsolePipeline</span><span class=\\\"o\\\">()).</span><span class=\\\"na\\\">run</span><span class=\\\"o\\\">();</span>\\n\" +\n            \"        <span class=\\\"o\\\">}</span>\\n\" +\n            \"    <span class=\\\"o\\\">}</span>\\n\" +\n            \"</pre></div>\\n\" +\n            \"\\n\" +\n            \"<ul>\\n\" +\n            \"<li>\\n\" +\n            \"<p><code>page.addTargetRequests(links)</code></p>\\n\" +\n            \"\\n\" +\n            \"<p>Add urls for crawling.</p>\\n\" +\n            \"</li>\\n\" +\n            \"</ul><p>You can also use annotation way:</p>\\n\" +\n            \"\\n\" +\n            \"<div class=\\\"highlight highlight-java\\\"><pre>    <span class=\\\"nd\\\">@TargetUrl</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"http://my.oschina.net/flashsword/blog/\\\\\\\\d+\\\"</span><span class=\\\"o\\\">)</span>\\n\" +\n            \"    <span class=\\\"kd\\\">public</span> <span class=\\\"kd\\\">class</span> <span class=\\\"nc\\\">OschinaBlog</span> <span class=\\\"o\\\">{</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"nd\\\">@ExtractBy</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"//title\\\"</span><span class=\\\"o\\\">)</span>\\n\" +\n            \"        <span class=\\\"kd\\\">private</span> <span class=\\\"n\\\">String</span> <span class=\\\"n\\\">title</span><span class=\\\"o\\\">;</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"nd\\\">@ExtractBy</span><span class=\\\"o\\\">(</span><span class=\\\"n\\\">value</span> <span class=\\\"o\\\">=</span> <span class=\\\"s\\\">\\\"div.BlogContent\\\"</span><span class=\\\"o\\\">,</span><span class=\\\"n\\\">type</span> <span class=\\\"o\\\">=</span> <span class=\\\"n\\\">ExtractBy</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">Type</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">Css</span><span class=\\\"o\\\">)</span>\\n\" +\n            \"        <span class=\\\"kd\\\">private</span> <span class=\\\"n\\\">String</span> <span class=\\\"n\\\">content</span><span class=\\\"o\\\">;</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"nd\\\">@ExtractBy</span><span class=\\\"o\\\">(</span><span class=\\\"n\\\">value</span> <span class=\\\"o\\\">=</span> <span class=\\\"s\\\">\\\"//div[@class='BlogTags']/a/text()\\\"</span><span class=\\\"o\\\">,</span> <span class=\\\"n\\\">multi</span> <span class=\\\"o\\\">=</span> <span class=\\\"kc\\\">true</span><span class=\\\"o\\\">)</span>\\n\" +\n            \"        <span class=\\\"kd\\\">private</span> <span class=\\\"n\\\">List</span><span class=\\\"o\\\">&lt;</span><span class=\\\"n\\\">String</span><span class=\\\"o\\\">&gt;</span> <span class=\\\"n\\\">tags</span><span class=\\\"o\\\">;</span>\\n\" +\n            \"\\n\" +\n            \"        <span class=\\\"kd\\\">public</span> <span class=\\\"kd\\\">static</span> <span class=\\\"kt\\\">void</span> <span class=\\\"nf\\\">main</span><span class=\\\"o\\\">(</span><span class=\\\"n\\\">String</span><span class=\\\"o\\\">[]</span> <span class=\\\"n\\\">args</span><span class=\\\"o\\\">)</span> <span class=\\\"o\\\">{</span>\\n\" +\n            \"            <span class=\\\"n\\\">OOSpider</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">create</span><span class=\\\"o\\\">(</span>\\n\" +\n            \"                <span class=\\\"n\\\">Site</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">me</span><span class=\\\"o\\\">().</span><span class=\\\"na\\\">addStartUrl</span><span class=\\\"o\\\">(</span><span class=\\\"s\\\">\\\"http://my.oschina.net/flashsword/blog\\\"</span><span class=\\\"o\\\">),</span>\\n\" +\n            \"                <span class=\\\"k\\\">new</span> <span class=\\\"nf\\\">ConsolePageModelPipeline</span><span class=\\\"o\\\">(),</span> <span class=\\\"n\\\">OschinaBlog</span><span class=\\\"o\\\">.</span><span class=\\\"na\\\">class</span><span class=\\\"o\\\">).</span><span class=\\\"na\\\">run</span><span class=\\\"o\\\">();</span>\\n\" +\n            \"        <span class=\\\"o\\\">}</span>\\n\" +\n            \"    <span class=\\\"o\\\">}</span>\\n\" +\n            \"</pre></div>\\n\" +\n            \"\\n\" +\n            \"<h3>\\n\" +\n            \"<a name=\\\"docs-and-samples\\\" class=\\\"anchor\\\" href=\\\"#docs-and-samples\\\"><span class=\\\"octicon octicon-link\\\"></span></a>Docs and samples:</h3>\\n\" +\n            \"\\n\" +\n            \"<p>The architecture of webmagic (refered to <a href=\\\"http://scrapy.org/\\\">Scrapy</a>)</p>\\n\" +\n            \"\\n\" +\n            \"<p><a href=\\\"https://github-camo.global.ssl.fastly.net/06cb8227231a6adf6d2a57b14b60a25389a25fe9/687474703a2f2f636f64653463726166742e6769746875622e696f2f696d616765732f706f7374732f7765626d616769632e706e67\\\" target=\\\"_blank\\\"><img src=\\\"https://github-camo.global.ssl.fastly.net/06cb8227231a6adf6d2a57b14b60a25389a25fe9/687474703a2f2f636f64653463726166742e6769746875622e696f2f696d616765732f706f7374732f7765626d616769632e706e67\\\" alt=\\\"image\\\" style=\\\"max-width:100%;\\\"></a></p>\\n\" +\n            \"\\n\" +\n            \"<p>Javadocs: <a href=\\\"http://code4craft.github.io/webmagic/docs/en/\\\">http://code4craft.github.io/webmagic/docs/en/</a></p>\\n\" +\n            \"\\n\" +\n            \"<p>There are some samples in <code>webmagic-samples</code> package.</p>\\n\" +\n            \"\\n\" +\n            \"<h3>\\n\" +\n            \"<a name=\\\"lisence\\\" class=\\\"anchor\\\" href=\\\"#lisence\\\"><span class=\\\"octicon octicon-link\\\"></span></a>Lisence:</h3>\\n\" +\n            \"\\n\" +\n            \"<p>Lisenced under <a href=\\\"http://opensource.org/licenses/Apache-2.0\\\">Apache 2.0 lisence</a></p>\\n\" +\n            \"\\n\" +\n            \"<h3>\\n\" +\n            \"<a name=\\\"thanks\\\" class=\\\"anchor\\\" href=\\\"#thanks\\\"><span class=\\\"octicon octicon-link\\\"></span></a>Thanks:</h3>\\n\" +\n            \"\\n\" +\n            \"<p>To write webmagic, I refered to the projects below :</p>\\n\" +\n            \"\\n\" +\n            \"<ul>\\n\" +\n            \"<li>\\n\" +\n            \"<p><strong>Scrapy</strong></p>\\n\" +\n            \"\\n\" +\n            \"<p>A crawler framework in Python.</p>\\n\" +\n            \"\\n\" +\n            \"<p><a href=\\\"http://scrapy.org/\\\">http://scrapy.org/</a></p>\\n\" +\n            \"</li>\\n\" +\n            \"<li>\\n\" +\n            \"<p><strong>Spiderman</strong></p>\\n\" +\n            \"\\n\" +\n            \"<p>Another crawler framework in Java.</p>\\n\" +\n            \"\\n\" +\n            \"<p><a href=\\\"https://gitcafe.com/laiweiwei/Spiderman\\\">https://gitcafe.com/laiweiwei/Spiderman</a></p>\\n\" +\n            \"</li>\\n\" +\n            \"</ul></article>\\n\" +\n            \"  </div>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"        </div>\\n\" +\n            \"\\n\" +\n            \"      </div><!-- /.repo-container -->\\n\" +\n            \"      <div class=\\\"modal-backdrop\\\"></div>\\n\" +\n            \"    </div><!-- /.container -->\\n\" +\n            \"  </div><!-- /.site -->\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"    </div><!-- /.wrapper -->\\n\" +\n            \"\\n\" +\n            \"      <div class=\\\"container\\\">\\n\" +\n            \"  <div class=\\\"site-footer\\\">\\n\" +\n            \"    <ul class=\\\"site-footer-links right\\\">\\n\" +\n            \"      <li><a href=\\\"https://status.github.com/\\\">Status</a></li>\\n\" +\n            \"      <li><a href=\\\"http://developer.github.com\\\">API</a></li>\\n\" +\n            \"      <li><a href=\\\"http://training.github.com\\\">Training</a></li>\\n\" +\n            \"      <li><a href=\\\"http://shop.github.com\\\">Shop</a></li>\\n\" +\n            \"      <li><a href=\\\"/blog\\\">Blog</a></li>\\n\" +\n            \"      <li><a href=\\\"/about\\\">About</a></li>\\n\" +\n            \"\\n\" +\n            \"    </ul>\\n\" +\n            \"\\n\" +\n            \"    <a href=\\\"/\\\">\\n\" +\n            \"      <span class=\\\"mega-octicon octicon-mark-github\\\"></span>\\n\" +\n            \"    </a>\\n\" +\n            \"\\n\" +\n            \"    <ul class=\\\"site-footer-links\\\">\\n\" +\n            \"      <li>&copy; 2013 <span title=\\\"0.04752s from github-fe114-cp1-prd.iad.github.net\\\">GitHub</span>, Inc.</li>\\n\" +\n            \"        <li><a href=\\\"/site/terms\\\">Terms</a></li>\\n\" +\n            \"        <li><a href=\\\"/site/privacy\\\">Privacy</a></li>\\n\" +\n            \"        <li><a href=\\\"/security\\\">Security</a></li>\\n\" +\n            \"        <li><a href=\\\"/contact\\\">Contact</a></li>\\n\" +\n            \"    </ul>\\n\" +\n            \"  </div><!-- /.site-footer -->\\n\" +\n            \"</div><!-- /.container -->\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"    <div class=\\\"fullscreen-overlay js-fullscreen-overlay\\\" id=\\\"fullscreen_overlay\\\">\\n\" +\n            \"  <div class=\\\"fullscreen-container js-fullscreen-container\\\">\\n\" +\n            \"    <div class=\\\"textarea-wrap\\\">\\n\" +\n            \"      <textarea name=\\\"fullscreen-contents\\\" id=\\\"fullscreen-contents\\\" class=\\\"js-fullscreen-contents\\\" placeholder=\\\"\\\" data-suggester=\\\"fullscreen_suggester\\\"></textarea>\\n\" +\n            \"          <div class=\\\"suggester-container\\\">\\n\" +\n            \"              <div class=\\\"suggester fullscreen-suggester js-navigation-container\\\" id=\\\"fullscreen_suggester\\\"\\n\" +\n            \"                 data-url=\\\"/code4craft/webmagic/suggestions/commit\\\">\\n\" +\n            \"              </div>\\n\" +\n            \"          </div>\\n\" +\n            \"    </div>\\n\" +\n            \"  </div>\\n\" +\n            \"  <div class=\\\"fullscreen-sidebar\\\">\\n\" +\n            \"    <a href=\\\"#\\\" class=\\\"exit-fullscreen js-exit-fullscreen tooltipped leftwards\\\" title=\\\"Exit Zen Mode\\\">\\n\" +\n            \"      <span class=\\\"mega-octicon octicon-screen-normal\\\"></span>\\n\" +\n            \"    </a>\\n\" +\n            \"    <a href=\\\"#\\\" class=\\\"theme-switcher js-theme-switcher tooltipped leftwards\\\"\\n\" +\n            \"      title=\\\"Switch themes\\\">\\n\" +\n            \"      <span class=\\\"octicon octicon-color-mode\\\"></span>\\n\" +\n            \"    </a>\\n\" +\n            \"  </div>\\n\" +\n            \"</div>\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"\\n\" +\n            \"    <div id=\\\"ajax-error-message\\\" class=\\\"flash flash-error\\\">\\n\" +\n            \"      <span class=\\\"octicon octicon-alert\\\"></span>\\n\" +\n            \"      <a href=\\\"#\\\" class=\\\"octicon octicon-remove-close close ajax-error-dismiss\\\"></a>\\n\" +\n            \"      Something went wrong with that request. Please try again.\\n\" +\n            \"    </div>\\n\" +\n            \"\\n\" +\n            \"  </body>\\n\" +\n            \"</html>\\n\" +\n            \"\\n\";\n    @Override\n    public Page download(Request request, Task task) {\n        Page page = new Page();\n        page.setRawText(html);\n        page.setStatusCode(200);\n        page.setDownloadSuccess(true);\n        page.setRequest(new Request(\"https://github.com/code4craft/webmagic\"));\n        page.setUrl(new PlainText(\"https://github.com/code4craft/webmagic\"));\n        return page;\n    }\n\n    @Override\n    public void setThread(int threadNum) {\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/formatter/DateFormatterTest.java",
    "content": "package us.codecraft.webmagic.formatter;\n\nimport org.apache.commons.lang3.time.DateFormatUtils;\nimport org.apache.commons.lang3.time.DateUtils;\nimport org.junit.Test;\nimport us.codecraft.webmagic.model.formatter.DateFormatter;\n\nimport java.util.Date;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class DateFormatterTest {\n\n    @Test\n    public void testDateFormatter() throws Exception {\n        DateFormatter dateFormatter = new DateFormatter();\n        String pattern = \"yyyy-MM-dd HH:mm\";\n        Date date = DateUtils.parseDate(\"2013-09-10 22:11\", new String[]{pattern});\n        dateFormatter.initParam(new String[]{pattern});\n        Date format = dateFormatter.format(DateFormatUtils.format(date, pattern));\n        assertThat(format).isEqualTo(date);\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/model/BaseRepo.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class BaseRepo {\n\n    @ExtractBy(\"//ul[@class='pagehead-actions']/li[1]//a[@class='social-count js-social-count']/text()\")\n    protected int star;\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/model/GithubRepo.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.HelpUrl;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.3.2\n */\n@TargetUrl(\"https://github.com/\\\\w+/\\\\w+\")\n@HelpUrl({\"https://github.com/\\\\w+\\\\?tab=repositories\", \"https://github.com/\\\\w+\", \"https://github.com/explore/*\"})\npublic class GithubRepo extends BaseRepo{\n\n    @ExtractBy(\"//ul[@class='pagehead-actions']/li[2]//a[@class='social-count']/text()\")\n    private int fork;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me().setSleepTime(100)\n                , new ConsolePageModelPipeline(), GithubRepo.class)\n                .addUrl(\"https://github.com/code4craft\").thread(10).run();\n    }\n\n    public int getStar() {\n        return star;\n    }\n\n    public int getFork() {\n        return fork;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/model/GithubRepoApi.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/6/3\n *         Time: 下午9:07\n */\npublic class GithubRepoApi {\n\n    @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$.name\",source = ExtractBy.Source.RawText)\n    private String name;\n\n    public String getName() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/model/GithubRepoTest.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.downloader.MockGithubDownloader;\nimport us.codecraft.webmagic.example.GithubRepo;\nimport us.codecraft.webmagic.pipeline.PageModelPipeline;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class GithubRepoTest {\n\n    @Test\n    public void test() {\n        OOSpider.create(Site.me().setSleepTime(0)\n                , new PageModelPipeline<GithubRepo>() {\n            @Override\n            public void process(GithubRepo o, Task task) {\n                assertThat(o.getStar()).isEqualTo(86);\n                assertThat(o.getFork()).isEqualTo(70);\n            }\n        }, GithubRepo.class).addUrl(\"https://github.com/code4craft/webmagic\").setDownloader(new MockGithubDownloader()).test(\"https://github.com/code4craft/webmagic\");\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/model/ModelPageProcessorTest.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.HelpUrl;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\nimport us.codecraft.webmagic.selector.PlainText;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class ModelPageProcessorTest {\n\n    private PageMocker pageMocker = new PageMocker();\n\n    @TargetUrl(\"http://codecraft.us/foo\")\n    public static class ModelFoo {\n\n        @ExtractBy(value = \"//div/@foo\", notNull = true)\n        private String foo;\n\n    }\n\n    @TargetUrl(\"http://codecraft.us/bar\")\n    public static class ModelBar {\n\n        @ExtractBy(value = \"//div/@bar\", notNull = true)\n        private String bar;\n\n    }\n\n    @TargetUrl(value = \"http://webmagic.io/foo/\\\\d+\",sourceRegion = \"//li[@class='bar']\")\n    @HelpUrl(value = \"http://webmagic.io/bar/\\\\d+\",sourceRegion = \"//li[@class='foo']\")\n    public static class MockModel {\n\n    }\n\n    @Test\n    public void testMultiModel_should_not_skip_when_match() throws Exception {\n        Page page = new Page();\n        page.setRawText(\"<div foo='foo'></div>\");\n        page.setRequest(new Request(\"http://codecraft.us/foo\"));\n        page.setUrl(PlainText.create(\"http://codecraft.us/foo\"));\n        ModelPageProcessor modelPageProcessor = ModelPageProcessor.create(null, ModelFoo.class, ModelBar.class);\n        modelPageProcessor.process(page);\n        assertThat(page.getResultItems().isSkip()).isFalse();\n    }\n\n    @Test\n    public void testExtractLinks() throws Exception {\n        ModelPageProcessor modelPageProcessor = ModelPageProcessor.create(null, MockModel.class);\n        Page page = pageMocker.getMockPage();\n        modelPageProcessor.process(page);\n        assertThat(page.getTargetRequests()).containsExactly(new Request(\"http://webmagic.io/bar/3\"), new Request(\"http://webmagic.io/bar/4\"), new Request(\"http://webmagic.io/foo/3\"), new Request(\"http://webmagic.io/foo/4\"));\n    }\n\n    @Test\n    public void testExtractNoLinks() throws Exception {\n        ModelPageProcessor modelPageProcessor = ModelPageProcessor.create(null, MockModel.class);\n        Page page = pageMocker.getMockPage();\n        modelPageProcessor.setExtractLinks(false);\n        modelPageProcessor.process(page);\n        assertThat(page.getTargetRequests()).isEmpty();\n    }\n\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/model/PageMapperTest.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/6/3\n *         Time: 下午3:23\n */\npublic class PageMapperTest {\n\n    private PageMocker pageMocker = new PageMocker();\n\n    @Test\n    public void test_get() throws Exception {\n        PageMapper<GithubRepoApi> pageMapper = new PageMapper<GithubRepoApi>(GithubRepoApi.class);\n        GithubRepoApi githubRepo = pageMapper.get(pageMocker.getMockJsonPage());\n        assertThat(githubRepo.getName()).isEqualTo(\"webmagic\");\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/model/PageMocker.java",
    "content": "package us.codecraft.webmagic.model;\n\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport org.apache.commons.io.IOUtils;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.selector.PlainText;\n\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/6/3\n *         Time: 下午9:08\n */\npublic class PageMocker {\n\n    public Page getMockJsonPage() throws IOException {\n        Page page = new Page();\n        page.setRawText(IOUtils.toString(PageMocker.class.getClassLoader().getResourceAsStream(\"json/mock-githubrepo.json\"), Charset.defaultCharset()));\n        page.setRequest(new Request(\"https://api.github.com/repos/code4craft/webmagic\"));\n        page.setUrl(new PlainText(\"https://api.github.com/repos/code4craft/webmagic\"));\n        return page;\n    }\n\n    public Page getMockPage() throws IOException {\n        Page page = new Page();\n        page.setRawText(IOUtils.toString(PageMocker.class.getClassLoader().getResourceAsStream(\"html/mock-webmagic.html\"), Charset.defaultCharset()));\n        page.setRequest(new Request(\"http://webmagic.io/list/0\"));\n        page.setUrl(new PlainText(\"http://webmagic.io/list/0\"));\n        return page;\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/model/PageModelExtractorTest.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport org.apache.commons.lang3.time.DateFormatUtils;\nimport org.apache.commons.lang3.time.DateUtils;\nimport org.junit.Test;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.ExtractByUrl;\nimport us.codecraft.webmagic.model.annotation.Formatter;\nimport us.codecraft.webmagic.model.formatter.DateFormatter;\n\nimport java.util.Date;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/6/3\n *         Time: 下午9:06\n */\npublic class PageModelExtractorTest {\n\n    private PageMocker pageMocker = new PageMocker();\n\n    public static class ModelDateStr {\n\n        @ExtractBy(value = \"//div[@class='date']/text()\", notNull = true)\n        private String dateStr;\n\n    }\n\n    public static class ModelDate {\n\n        @Formatter(value = \"yyyyMMdd\", formatter = DateFormatter.class)\n        @ExtractBy(value = \"//div[@class='date']/text()\", notNull = true)\n        private Date date;\n\n    }\n\n    public static class ModelInt {\n\n        @ExtractBy(value = \"//div[@class='number']/text()\", notNull = true)\n        private int number;\n\n    }\n\n    public static class ModelStringList {\n\n        @ExtractBy(\"//li[@class='list']/a/@href\")\n        private List<String> links;\n\n    }\n\n    public static class ModelIntList {\n\n        @Formatter(subClazz = Integer.class)\n        @ExtractBy(\"//li[@class='numbers']/text()\")\n        private List<Integer> numbers;\n\n    }\n\n    public static class ModelDateList {\n\n        @Formatter(subClazz = Date.class, value = \"yyyyMMdd\")\n        @ExtractBy(\"//li[@class='dates']/text()\")\n        private List<Date> dates;\n\n    }\n\n    public static class ModelCustomList {\n\n        @Formatter(subClazz = Date.class, value = \"yyyyMMdd\",formatter = DateFormatter.class)\n        @ExtractBy(\"//li[@class='dates']/text()\")\n        private List<Date> dates;\n\n    }\n\n    public static class ModelJsonStr {\n\n        @ExtractBy(type = ExtractBy.Type.JsonPath, value = \"$.name\")\n        private String name;\n\n    }\n\n    public static class ModelUrl {\n\n        @ExtractByUrl(\"https://api\\\\.github\\\\.com/repos/\\\\w+/(\\\\w+)\")\n        private String name;\n\n    }\n\n    @Test\n    public void testXpath() throws Exception {\n        ModelDateStr modelDate = (ModelDateStr) PageModelExtractor.create(ModelDateStr.class).process(pageMocker.getMockPage());\n        assertThat(modelDate.dateStr).isEqualTo(\"20170603\");\n    }\n\n    @Test\n    public void testExtractDate() throws Exception {\n        ModelDate modelDate = (ModelDate) PageModelExtractor.create(ModelDate.class).process(pageMocker.getMockPage());\n        assertThat(DateFormatUtils.format(modelDate.date,\"yyyyMMdd\")).isEqualTo(\"20170603\");\n    }\n\n    @Test\n    public void testExtractInt() throws Exception {\n        ModelInt modelDate = (ModelInt) PageModelExtractor.create(ModelInt.class).process(pageMocker.getMockPage());\n        assertThat(modelDate.number).isEqualTo(12);\n    }\n\n    @Test\n    public void testExtractList() throws Exception {\n        ModelStringList modelDate = (ModelStringList) PageModelExtractor.create(ModelStringList.class).process(pageMocker.getMockPage());\n        assertThat(modelDate.links).containsExactly(\"http://webmagic.io/list/1\",\"http://webmagic.io/list/2\",\"http://webmagic.io/list/3\",\"http://webmagic.io/list/4\");\n    }\n\n    @Test\n    public void testExtractIntList() throws Exception {\n        ModelIntList modelDate = (ModelIntList) PageModelExtractor.create(ModelIntList.class).process(pageMocker.getMockPage());\n        assertThat(modelDate.numbers).containsExactly(1,2,3,4);\n    }\n\n    @Test\n    public void testExtractDateList() throws Exception {\n        ModelDateList modelDate = (ModelDateList) PageModelExtractor.create(ModelDateList.class).process(pageMocker.getMockPage());\n        assertThat(modelDate.dates).containsExactly(DateUtils.parseDate(\"20170601\", \"yyyyMMdd\"), DateUtils.parseDate(\"20170602\", \"yyyyMMdd\"), DateUtils.parseDate(\"20170603\", \"yyyyMMdd\"), DateUtils.parseDate(\"20170604\", \"yyyyMMdd\"));\n    }\n\n    @Test\n    public void testExtractCustomList() throws Exception {\n        ModelCustomList modelDate = (ModelCustomList) PageModelExtractor.create(ModelCustomList.class).process(pageMocker.getMockPage());\n        assertThat(modelDate.dates).containsExactly(DateUtils.parseDate(\"20170601\", \"yyyyMMdd\"), DateUtils.parseDate(\"20170602\", \"yyyyMMdd\"), DateUtils.parseDate(\"20170603\", \"yyyyMMdd\"), DateUtils.parseDate(\"20170604\", \"yyyyMMdd\"));\n    }\n\n    @Test\n    public void testExtractJson() throws Exception {\n        ModelJsonStr modelDate = (ModelJsonStr) PageModelExtractor.create(ModelJsonStr.class).process(pageMocker.getMockJsonPage());\n        assertThat(modelDate.name).isEqualTo(\"webmagic\");\n    }\n\n    @Test\n    public void testExtractByUrl() throws Exception {\n        ModelUrl modelDate = (ModelUrl) PageModelExtractor.create(ModelUrl.class).process(pageMocker.getMockJsonPage());\n        assertThat(modelDate.name).isEqualTo(\"webmagic\");\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/monitor/CustomSpiderStatus.java",
    "content": "package us.codecraft.webmagic.monitor;\n\nimport us.codecraft.webmagic.Spider;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class CustomSpiderStatus extends SpiderStatus implements CustomSpiderStatusMXBean {\n\n    public CustomSpiderStatus(Spider spider, SpiderMonitor.MonitorSpiderListener monitorSpiderListener) {\n        super(spider, monitorSpiderListener);\n    }\n\n\n    @Override\n    public String getSchedulerName() {\n        return spider.getScheduler().getClass().getName();\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/monitor/CustomSpiderStatusMXBean.java",
    "content": "package us.codecraft.webmagic.monitor;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic interface CustomSpiderStatusMXBean extends SpiderStatusMXBean {\n\n    public String getSchedulerName();\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/monitor/SeedUrlWithPortTest.java",
    "content": "package us.codecraft.webmagic.monitor;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport javax.management.JMException;\n\n/**\n * @author jerry_shenchao@163.com\n */\npublic class SeedUrlWithPortTest {\n\n    @Test\n    public void testSeedUrlWithPort() throws JMException {\n        Spider spider = Spider.create(new TempProcessor()).addUrl(\"http://www.hndpf.org:8889/\");\n        SpiderMonitor.instance().register(spider);\n        spider.run();\n    }\n}\n\nclass TempProcessor implements PageProcessor {\n\n    @Override\n    public void process(Page page) {\n\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me();\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/monitor/SpiderMonitorTest.java",
    "content": "package us.codecraft.webmagic.monitor;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.example.GithubRepoPageProcessor;\nimport us.codecraft.webmagic.processor.example.ZhihuPageProcessor;\n\n/**\n * @author code4crafer@gmail.com\n * @since 0.5.0\n */\npublic class SpiderMonitorTest {\n\n    @Test\n    public void testInherit() throws Exception {\n        SpiderMonitor spiderMonitor = new SpiderMonitor(){\n            @Override\n            protected SpiderStatusMXBean getSpiderStatusMBean(Spider spider, MonitorSpiderListener monitorSpiderListener) {\n                return new CustomSpiderStatus(spider, monitorSpiderListener);\n            }\n        };\n\n        Spider zhihuSpider = Spider.create(new ZhihuPageProcessor())\n                .addUrl(\"http://my.oschina.net/flashsword/blog\").thread(2);\n        Spider githubSpider = Spider.create(new GithubRepoPageProcessor())\n                .addUrl(\"https://github.com/code4craft\");\n\n        spiderMonitor.register(zhihuSpider, githubSpider);\n\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/processor/GithubRepoProcessor.java",
    "content": "package us.codecraft.webmagic.processor;\n\nimport junit.framework.Assert;\nimport org.junit.Test;\nimport us.codecraft.webmagic.*;\nimport us.codecraft.webmagic.downloader.MockGithubDownloader;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.pipeline.Pipeline;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class GithubRepoProcessor implements PageProcessor {\n    @Override\n    public void process(Page page) {\n        page.putField(\"star\",page.getHtml().xpath(\"//ul[@class='pagehead-actions']/li[2]//a[@class='social-count js-social-count']/text()\").toString());\n        page.putField(\"fork\",page.getHtml().xpath(\"//ul[@class='pagehead-actions']/li[3]//a[@class='social-count']/text()\").toString());\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me();\n    }\n\n    @Test\n    public void test() {\n        OOSpider.create(new GithubRepoProcessor()).addPipeline(new Pipeline() {\n            @Override\n            public void process(ResultItems resultItems, Task task) {\n                Assert.assertEquals(\"78\",((String)resultItems.get(\"star\")).trim());\n                Assert.assertEquals(\"65\",((String)resultItems.get(\"fork\")).trim());\n            }\n        }).setDownloader(new MockGithubDownloader()).test(\"https://github.com/code4craft/webmagic\");\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/scheduler/BloomFilterDuplicateRemoverTest.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.scheduler.component.DuplicateRemover;\nimport us.codecraft.webmagic.scheduler.component.HashSetDuplicateRemover;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class BloomFilterDuplicateRemoverTest {\n\n    @Test\n    public void testRemove() throws Exception {\n        BloomFilterDuplicateRemover bloomFilterDuplicateRemover = new BloomFilterDuplicateRemover(10);\n        boolean isDuplicate = bloomFilterDuplicateRemover.isDuplicate(new Request(\"a\"), null);\n        assertThat(isDuplicate).isFalse();\n        isDuplicate = bloomFilterDuplicateRemover.isDuplicate(new Request(\"a\"), null);\n        assertThat(isDuplicate).isTrue();\n        isDuplicate = bloomFilterDuplicateRemover.isDuplicate(new Request(\"b\"), null);\n        assertThat(isDuplicate).isFalse();\n        isDuplicate = bloomFilterDuplicateRemover.isDuplicate(new Request(\"b\"), null);\n        assertThat(isDuplicate).isTrue();\n\n    }\n\n    @Ignore(\"long time\")\n    @Test\n    public void testMemory() throws Exception {\n        int times = 5000000;\n        DuplicateRemover duplicateRemover = new BloomFilterDuplicateRemover(times,0.005);\n        long freeMemory = Runtime.getRuntime().freeMemory();\n        long time = System.currentTimeMillis();\n        for (int i = 0; i < times; i++) {\n            duplicateRemover.isDuplicate(new Request(String.valueOf(i)), null);\n        }\n        System.out.println(\"Time used by bloomfilter:\" + (System.currentTimeMillis() - time));\n        System.out.println(\"Memory used by bloomfilter:\" + (freeMemory - Runtime.getRuntime().freeMemory()));\n\n        duplicateRemover = new HashSetDuplicateRemover();\n        System.gc();\n        freeMemory = Runtime.getRuntime().freeMemory();\n        time = System.currentTimeMillis();\n        for (int i = 0; i < times; i++) {\n            duplicateRemover.isDuplicate(new Request(String.valueOf(i)), null);\n        }\n        System.out.println(\"Time used by hashset:\" + (System.currentTimeMillis() - time));\n        System.out.println(\"Memory used by hashset:\" + (freeMemory - Runtime.getRuntime().freeMemory()));\n    }\n\n    @Ignore(\"long time\")\n    @Test\n    public void testMissHit() throws Exception {\n        int times = 5000000;\n        DuplicateRemover duplicateRemover = new BloomFilterDuplicateRemover(times, 0.01);\n        int right = 0;\n        int wrong = 0;\n        int missCheck = 0;\n        for (int i = 0; i < times; i++) {\n            boolean duplicate = duplicateRemover.isDuplicate(new Request(String.valueOf(i)), null);\n            if (duplicate) {\n                wrong++;\n            } else {\n                right++;\n            }\n            duplicate = duplicateRemover.isDuplicate(new Request(String.valueOf(i)), null);\n            if (!duplicate) {\n                missCheck++;\n            }\n        }\n\n        System.out.println(\"Right count: \" + right + \" Wrong count: \" + wrong + \" Miss check: \" + missCheck);\n    }\n\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/scheduler/RedisPrioritySchedulerTest.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\n\n/**\n * @author sai\n * Created by sai on 16-7-5.\n */\npublic class RedisPrioritySchedulerTest\n{\n\n    private RedisPriorityScheduler scheduler;\n\n    @Before\n    public void setUp()\n    {\n        scheduler = new RedisPriorityScheduler(\"localhost\");\n    }\n\n    @Ignore(\"environment depended\")\n    @Test\n    public void test()\n    {\n        Task task = new Task() {\n            @Override\n            public String getUUID() {\n                return \"TestTask\";\n            }\n\n            @Override\n            public Site getSite() {\n                return null;\n            }\n        };\n\n        scheduler.resetDuplicateCheck(task);\n\n        Request request = new Request(\"https://www.google.com\");\n        Request request1= new Request(\"https://www.facebook.com/\");\n        Request request2= new Request(\"https://twitter.com\");\n\n        request.setPriority(1).putExtra(\"name\", \"google\");\n        request1.setPriority(0).putExtra(\"name\", \"facebook\");\n        request2.setPriority(-1).putExtra(\"name\", \"twitter\");\n\n        scheduler.push(request, task);\n        scheduler.push(request1, task);\n        scheduler.push(request2, task);\n\n        Request GRequest    = scheduler.poll(task);\n        Request FBRequest   = scheduler.poll(task);\n        Request TRequest    = scheduler.poll(task);\n\n        Assert.assertEquals(GRequest.getUrl(), request.getUrl());\n        Assert.assertEquals(GRequest.getExtra(\"name\"), request.getExtra(\"name\"));\n\n        Assert.assertEquals(FBRequest.getUrl(), request1.getUrl());\n        Assert.assertEquals(FBRequest.getExtra(\"name\"), request.getExtra(\"name\"));\n\n        Assert.assertEquals(TRequest.getUrl(), request2.getUrl());\n        Assert.assertEquals(TRequest.getExtra(\"name\"), request.getExtra(\"name\"));\n    }\n\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/scheduler/RedisSchedulerTest.java",
    "content": "package us.codecraft.webmagic.scheduler;\n\nimport org.junit.Before;\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class RedisSchedulerTest {\n\n    private RedisScheduler redisScheduler;\n\n    @Before\n    public void setUp() {\n        redisScheduler = new RedisScheduler(\"localhost\");\n    }\n\n    @Ignore(\"environment depended\")\n    @Test\n    public void test() {\n        Task task = new Task() {\n            @Override\n            public String getUUID() {\n                return \"1\";\n            }\n\n            @Override\n            public Site getSite() {\n                return null;\n            }\n        };\n        Request request = new Request(\"http://www.ibm.com/developerworks/cn/java/j-javadev2-22/\");\n        request.putExtra(\"1\",\"2\");\n        redisScheduler.push(request, task);\n        Request poll = redisScheduler.poll(task);\n        assertThat(poll).isEqualTo(request);\n\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/utils/IPUtilsTest.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport org.junit.Test;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class IPUtilsTest {\n\n    @Test\n    public void testGetFirstNoLoopbackIPAddresses() throws Exception {\n        System.out.println(IPUtils.getFirstNoLoopbackIPAddresses());\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/java/us/codecraft/webmagic/utils/RequestUtilsTest.java",
    "content": "package us.codecraft.webmagic.utils;\n\nimport org.junit.Test;\nimport us.codecraft.webmagic.Request;\n\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author code4crafter@gmail.com\n *         Date: 2017/6/5\n *         Time: 下午5:08\n */\npublic class RequestUtilsTest {\n\n    @Test\n    public void test_generate_range() throws Exception {\n        List<Request> requests = RequestUtils.from(\"http://angularjs.cn/api/article/latest?p=[1-3]&s=20\");\n        assertThat(requests).containsExactly(new Request(\"http://angularjs.cn/api/article/latest?p=1&s=20\"), new Request(\"http://angularjs.cn/api/article/latest?p=2&s=20\"), new Request(\"http://angularjs.cn/api/article/latest?p=3&s=20\"));\n    }\n\n    @Test\n    public void test_generate_range_when_invalid_number() throws Exception {\n        List<Request> requests = RequestUtils.from(\"http://angularjs.cn/api/article/latest?p=[10-3]&s=20\");\n        assertThat(requests).isEmpty();\n    }\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/resources/html/mock-github.html",
    "content": "\n\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\" class=\" is-u2f-enabled\">\n<head prefix=\"og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#\">\n    <meta charset='utf-8'>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta http-equiv=\"Content-Language\" content=\"en\">\n    <meta name=\"viewport\" content=\"width=1020\">\n\n\n    <title>code4craft/webmagic</title>\n    <link rel=\"search\" type=\"application/opensearchdescription+xml\" href=\"/opensearch.xml\" title=\"GitHub\">\n    <link rel=\"fluid-icon\" href=\"https://github.com/fluidicon.png\" title=\"GitHub\">\n    <link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"/apple-touch-icon-114.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"/apple-touch-icon-114.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"/apple-touch-icon-144.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"/apple-touch-icon-144.png\">\n    <meta property=\"fb:app_id\" content=\"1401488693436528\">\n\n    <meta content=\"@github\" name=\"twitter:site\" /><meta content=\"summary\" name=\"twitter:card\" /><meta content=\"code4craft/webmagic\" name=\"twitter:title\" /><meta content=\"webmagic - A scalable web crawler framework.\" name=\"twitter:description\" /><meta content=\"https://avatars2.githubusercontent.com/u/1351884?v=3&amp;s=400\" name=\"twitter:image:src\" />\n    <meta content=\"GitHub\" property=\"og:site_name\" /><meta content=\"object\" property=\"og:type\" /><meta content=\"https://avatars2.githubusercontent.com/u/1351884?v=3&amp;s=400\" property=\"og:image\" /><meta content=\"code4craft/webmagic\" property=\"og:title\" /><meta content=\"https://github.com/code4craft/webmagic\" property=\"og:url\" /><meta content=\"webmagic - A scalable web crawler framework.\" property=\"og:description\" />\n    <meta name=\"browser-stats-url\" content=\"https://api.github.com/_private/browser/stats\">\n    <meta name=\"browser-errors-url\" content=\"https://api.github.com/_private/browser/errors\">\n    <link rel=\"assets\" href=\"https://assets-cdn.github.com/\">\n    <link rel=\"web-socket\" href=\"wss://live.github.com/_sockets/MTM1MTg4NDo3YWI4NmUwOGM3MzhlMjU5MzVhZGNiNmFmOWUxNjExNTpjNWZlMzRmNzk5NjE4NGMxNDQwZDMzY2Q5ZWE3NGRmMmZkZWMwYTg2NTRkOTA2YTU2Mjk5NDYxYTk1ZjljNDJj--22ca52337ffde7621f032b082bfd863eeade6f9c\">\n    <meta name=\"pjax-timeout\" content=\"1000\">\n    <link rel=\"sudo-modal\" href=\"/sessions/sudo_modal\">\n\n    <meta name=\"msapplication-TileImage\" content=\"/windows-tile.png\">\n    <meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n    <meta name=\"selected-link\" value=\"repo_source\" data-pjax-transient>\n\n    <meta name=\"google-site-verification\" content=\"KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU\">\n    <meta name=\"google-analytics\" content=\"UA-3769691-2\">\n\n    <meta content=\"collector.githubapp.com\" name=\"octolytics-host\" /><meta content=\"github\" name=\"octolytics-app-id\" /><meta content=\"6AB91C29:10EF:6D4972F:569D042D\" name=\"octolytics-dimension-request_id\" /><meta content=\"1351884\" name=\"octolytics-actor-id\" /><meta content=\"code4craft\" name=\"octolytics-actor-login\" /><meta content=\"b87866a7952857ad32eeb0a33a8d3f9743660184e01113bc601ed02f292f8597\" name=\"octolytics-actor-hash\" />\n    <meta content=\"/&lt;user-name&gt;/&lt;repo-name&gt;\" data-pjax-transient=\"true\" name=\"analytics-location\" />\n    <meta content=\"Rails, view, files#disambiguate\" data-pjax-transient=\"true\" name=\"analytics-event\" />\n\n\n    <meta class=\"js-ga-set\" name=\"dimension1\" content=\"Logged In\">\n\n\n\n    <meta name=\"hostname\" content=\"github.com\">\n    <meta name=\"user-login\" content=\"code4craft\">\n\n    <meta name=\"expected-hostname\" content=\"github.com\">\n\n    <link rel=\"mask-icon\" href=\"https://assets-cdn.github.com/pinned-octocat.svg\" color=\"#4078c0\">\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"https://assets-cdn.github.com/favicon.ico\">\n\n    <meta content=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" name=\"form-nonce\" />\n\n    <link crossorigin=\"anonymous\" href=\"https://assets-cdn.github.com/assets/github-1b53a0bcb9add868a6c5ae469ecabb8b236ffa8f2b05360fde027f75eb714f1b.css\" media=\"all\" rel=\"stylesheet\" />\n    <link crossorigin=\"anonymous\" href=\"https://assets-cdn.github.com/assets/github2-70af51f1bed4904749e6ef486ad11871c8ce4361ac82bb5f96a090b7f5346580.css\" media=\"all\" rel=\"stylesheet\" />\n\n\n\n\n    <meta http-equiv=\"x-pjax-version\" content=\"4222bfcb881548243f94e18e8a3bcfd0\">\n\n\n    <meta name=\"description\" content=\"webmagic - A scalable web crawler framework.\">\n    <meta name=\"go-import\" content=\"github.com/code4craft/webmagic git https://github.com/code4craft/webmagic.git\">\n\n    <meta content=\"1351884\" name=\"octolytics-dimension-user_id\" /><meta content=\"code4craft\" name=\"octolytics-dimension-user_login\" /><meta content=\"9623064\" name=\"octolytics-dimension-repository_id\" /><meta content=\"code4craft/webmagic\" name=\"octolytics-dimension-repository_nwo\" /><meta content=\"true\" name=\"octolytics-dimension-repository_public\" /><meta content=\"false\" name=\"octolytics-dimension-repository_is_fork\" /><meta content=\"9623064\" name=\"octolytics-dimension-repository_network_root_id\" /><meta content=\"code4craft/webmagic\" name=\"octolytics-dimension-repository_network_root_nwo\" />\n    <link href=\"https://github.com/code4craft/webmagic/commits/master.atom\" rel=\"alternate\" title=\"Recent Commits to webmagic:master\" type=\"application/atom+xml\">\n\n</head>\n\n\n<body class=\"logged_in   env-production macintosh vis-public\">\n<a href=\"#start-of-content\" tabindex=\"1\" class=\"accessibility-aid js-skip-to-content\">Skip to content</a>\n\n\n\n\n\n\n\n<div class=\"header header-logged-in true\" role=\"banner\">\n    <div class=\"container clearfix\">\n\n        <a class=\"header-logo-invertocat\" href=\"https://github.com/\" data-hotkey=\"g d\" aria-label=\"Homepage\" data-ga-click=\"Header, go to dashboard, icon:logo\">\n            <span aria-hidden=\"true\" class=\"mega-octicon octicon-mark-github\"></span>\n        </a>\n\n\n        <div class=\"site-search repo-scope js-site-search\" role=\"search\">\n            <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/search\" class=\"js-site-search-form\" data-global-search-url=\"/search\" data-repo-search-url=\"/code4craft/webmagic/search\" method=\"get\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /></div>\n            <label class=\"js-chromeless-input-container form-control\">\n                <div class=\"scope-badge\">This repository</div>\n                <input type=\"text\"\n                       class=\"js-site-search-focus js-site-search-field is-clearable chromeless-input\"\n                       data-hotkey=\"s\"\n                       name=\"q\"\n                       placeholder=\"Search\"\n                       aria-label=\"Search this repository\"\n                       data-global-scope-placeholder=\"Search GitHub\"\n                       data-repo-scope-placeholder=\"Search\"\n                       tabindex=\"1\"\n                       autocapitalize=\"off\">\n            </label>\n        </form>\n        </div>\n\n        <ul class=\"header-nav left\" role=\"navigation\">\n            <li class=\"header-nav-item\">\n                <a href=\"/pulls\" class=\"js-selected-navigation-item header-nav-link\" data-ga-click=\"Header, click, Nav menu - item:pulls context:user\" data-hotkey=\"g p\" data-selected-links=\"/pulls /pulls/assigned /pulls/mentioned /pulls\">\n                    Pull requests\n                </a>        </li>\n            <li class=\"header-nav-item\">\n                <a href=\"/issues\" class=\"js-selected-navigation-item header-nav-link\" data-ga-click=\"Header, click, Nav menu - item:issues context:user\" data-hotkey=\"g i\" data-selected-links=\"/issues /issues/assigned /issues/mentioned /issues\">\n                    Issues\n                </a>        </li>\n            <li class=\"header-nav-item\">\n                <a class=\"header-nav-link\" href=\"https://gist.github.com/\" data-ga-click=\"Header, go to gist, text:gist\">Gist</a>\n            </li>\n        </ul>\n\n\n        <ul class=\"header-nav user-nav right\" id=\"user-links\">\n            <li class=\"header-nav-item\">\n      <span class=\"js-socket-channel js-updatable-content\"\n            data-channel=\"notification-changed:code4craft\"\n            data-url=\"/notifications/header\">\n      <a href=\"/notifications\" aria-label=\"You have no unread notifications\" class=\"header-nav-link notification-indicator tooltipped tooltipped-s\" data-ga-click=\"Header, go to notifications, icon:read\" data-hotkey=\"g n\">\n          <span class=\"mail-status all-read\"></span>\n          <span aria-hidden=\"true\" class=\"octicon octicon-bell\"></span>\n      </a>  </span>\n\n            </li>\n\n            <li class=\"header-nav-item dropdown js-menu-container\">\n                <a class=\"header-nav-link tooltipped tooltipped-s js-menu-target\" href=\"/new\"\n                   aria-label=\"Create new…\"\n                   data-ga-click=\"Header, create new, icon:add\">\n                    <span aria-hidden=\"true\" class=\"octicon octicon-plus left\"></span>\n                    <span class=\"dropdown-caret\"></span>\n                </a>\n\n                <div class=\"dropdown-menu-content js-menu-content\">\n                    <ul class=\"dropdown-menu dropdown-menu-sw\">\n\n                        <a class=\"dropdown-item\" href=\"/new\" data-ga-click=\"Header, create new repository\">\n                            New repository\n                        </a>\n\n\n                        <a class=\"dropdown-item\" href=\"/organizations/new\" data-ga-click=\"Header, create new organization\">\n                            New organization\n                        </a>\n\n\n\n                        <div class=\"dropdown-divider\"></div>\n                        <div class=\"dropdown-header\">\n                            <span title=\"code4craft/webmagic\">This repository</span>\n                        </div>\n                        <a class=\"dropdown-item\" href=\"/code4craft/webmagic/issues/new\" data-ga-click=\"Header, create new issue\">\n                            New issue\n                        </a>\n                        <a class=\"dropdown-item\" href=\"/code4craft/webmagic/settings/collaboration\" data-ga-click=\"Header, create new collaborator\">\n                            New collaborator\n                        </a>\n\n                    </ul>\n                </div>\n            </li>\n\n            <li class=\"header-nav-item dropdown js-menu-container\">\n                <a class=\"header-nav-link name tooltipped tooltipped-sw js-menu-target\" href=\"/code4craft\"\n                   aria-label=\"View profile and more\"\n                   data-ga-click=\"Header, show menu, icon:avatar\">\n                    <img alt=\"@code4craft\" class=\"avatar\" height=\"20\" src=\"https://avatars2.githubusercontent.com/u/1351884?v=3&amp;s=40\" width=\"20\" />\n                    <span class=\"dropdown-caret\"></span>\n                </a>\n\n                <div class=\"dropdown-menu-content js-menu-content\">\n                    <div class=\"dropdown-menu  dropdown-menu-sw\">\n                        <div class=\" dropdown-header header-nav-current-user css-truncate\">\n                            Signed in as <strong class=\"css-truncate-target\">code4craft</strong>\n\n                        </div>\n\n\n                        <div class=\"dropdown-divider\"></div>\n\n                        <a class=\"dropdown-item\" href=\"/code4craft\" data-ga-click=\"Header, go to profile, text:your profile\">\n                            Your profile\n                        </a>\n                        <a class=\"dropdown-item\" href=\"/stars\" data-ga-click=\"Header, go to starred repos, text:your stars\">\n                            Your stars\n                        </a>\n                        <a class=\"dropdown-item\" href=\"/explore\" data-ga-click=\"Header, go to explore, text:explore\">\n                            Explore\n                        </a>\n                        <a class=\"dropdown-item\" href=\"/integrations\" data-ga-click=\"Header, go to integrations, text:integrations\">\n                            Integrations\n                        </a>\n                        <a class=\"dropdown-item\" href=\"https://help.github.com\" data-ga-click=\"Header, go to help, text:help\">\n                            Help\n                        </a>\n\n                        <div class=\"dropdown-divider\"></div>\n\n                        <a class=\"dropdown-item\" href=\"/settings/profile\" data-ga-click=\"Header, go to settings, icon:settings\">\n                            Settings\n                        </a>\n\n                        <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/logout\" class=\"logout-form\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"ZINKeCzFexhof31oC9cCA+iEXymQ95S66nGpEO1oOhr5jI03Z1aD4k6dtjVPp11IJlwY9sSGIpgQt/SthVhr5Q==\" /></div>\n                        <button class=\"dropdown-item dropdown-signout\" data-ga-click=\"Header, sign out, icon:logout\">\n                            Sign out\n                        </button>\n                    </form>\n                    </div>\n                </div>\n            </li>\n        </ul>\n\n\n\n    </div>\n</div>\n\n\n\n\n\n\n<div id=\"start-of-content\" class=\"accessibility-aid\"></div>\n\n<div id=\"js-flash-container\">\n</div>\n\n\n<div role=\"main\" class=\"main-content\">\n    <div itemscope itemtype=\"http://schema.org/WebPage\">\n        <div id=\"js-repo-pjax-container\" class=\"context-loader-container js-repo-nav-next\" data-pjax-container>\n\n            <div class=\"pagehead repohead instapaper_ignore readability-menu experiment-repo-nav\">\n                <div class=\"container repohead-details-container\">\n\n\n\n                    <ul class=\"pagehead-actions\">\n\n                        <li>\n                            <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/notifications/subscribe\" class=\"js-social-container\" data-autosubmit=\"true\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" data-remote=\"true\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"A8U/nsuWmrNcDVP1LvjcaT2gKFrPqnmC5eOwH18NcsePFGlsinj0uaf9yaNxnk741gXv+8QIVEYn0veSA3qRUQ==\" /></div>      <input id=\"repository_id\" name=\"repository_id\" type=\"hidden\" value=\"9623064\" />\n\n                            <div class=\"select-menu js-menu-container js-select-menu\">\n                                <a href=\"/code4craft/webmagic/subscription\"\n                                   class=\"btn btn-sm btn-with-count select-menu-button js-menu-target\" role=\"button\" tabindex=\"0\" aria-haspopup=\"true\"\n                                   data-ga-click=\"Repository, click Watch settings, action:files#disambiguate\">\n            <span class=\"js-select-button\">\n              <span aria-hidden=\"true\" class=\"octicon octicon-eye\"></span>\n              Unwatch\n            </span>\n                                </a>\n                                <a class=\"social-count js-social-count\" href=\"/code4craft/webmagic/watchers\">\n                                    367\n                                </a>\n\n                                <div class=\"select-menu-modal-holder\">\n                                    <div class=\"select-menu-modal subscription-menu-modal js-menu-content\" aria-hidden=\"true\">\n                                        <div class=\"select-menu-header\">\n                                            <span aria-label=\"Close\" class=\"octicon octicon-x js-menu-close\" role=\"button\"></span>\n                                            <span class=\"select-menu-title\">Notifications</span>\n                                        </div>\n\n                                        <div class=\"select-menu-list js-navigation-container\" role=\"menu\">\n\n                                            <div class=\"select-menu-item js-navigation-item \" role=\"menuitem\" tabindex=\"0\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                <div class=\"select-menu-item-text\">\n                                                    <input id=\"do_included\" name=\"do\" type=\"radio\" value=\"included\" />\n                                                    <span class=\"select-menu-item-heading\">Not watching</span>\n                                                    <span class=\"description\">Be notified when participating or @mentioned.</span>\n                    <span class=\"js-select-button-text hidden-select-button-text\">\n                      <span aria-hidden=\"true\" class=\"octicon octicon-eye\"></span>\n                      Watch\n                    </span>\n                                                </div>\n                                            </div>\n\n                                            <div class=\"select-menu-item js-navigation-item selected\" role=\"menuitem\" tabindex=\"0\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                <div class=\"select-menu-item-text\">\n                                                    <input checked=\"checked\" id=\"do_subscribed\" name=\"do\" type=\"radio\" value=\"subscribed\" />\n                                                    <span class=\"select-menu-item-heading\">Watching</span>\n                                                    <span class=\"description\">Be notified of all conversations.</span>\n                    <span class=\"js-select-button-text hidden-select-button-text\">\n                      <span aria-hidden=\"true\" class=\"octicon octicon-eye\"></span>\n                      Unwatch\n                    </span>\n                                                </div>\n                                            </div>\n\n                                            <div class=\"select-menu-item js-navigation-item \" role=\"menuitem\" tabindex=\"0\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                <div class=\"select-menu-item-text\">\n                                                    <input id=\"do_ignore\" name=\"do\" type=\"radio\" value=\"ignore\" />\n                                                    <span class=\"select-menu-item-heading\">Ignoring</span>\n                                                    <span class=\"description\">Never be notified.</span>\n                    <span class=\"js-select-button-text hidden-select-button-text\">\n                      <span aria-hidden=\"true\" class=\"octicon octicon-mute\"></span>\n                      Stop ignoring\n                    </span>\n                                                </div>\n                                            </div>\n\n                                        </div>\n\n                                    </div>\n                                </div>\n                            </div>\n                        </form>\n                        </li>\n\n                        <li>\n\n                            <div class=\"js-toggler-container js-social-container starring-container \">\n\n                                <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/unstar\" class=\"js-toggler-form starred js-unstar-button\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" data-remote=\"true\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"mGh0BvguuVTHUZ1Lnf51zYVJ7dGdABVF+Bavja/Jqy7OjG/oveUKfauEqgIowVAM3UFe636pTW6E8jHFtSR0Aw==\" /></div>\n                                <button\n                                        class=\"btn btn-sm btn-with-count js-toggler-target\"\n                                        aria-label=\"Unstar this repository\" title=\"Unstar code4craft/webmagic\"\n                                        data-ga-click=\"Repository, click unstar button, action:files#disambiguate; text:Unstar\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-star\"></span>\n                                    Unstar\n                                </button>\n                                <a class=\"social-count js-social-count\" href=\"/code4craft/webmagic/stargazers\">\n                                    1,743\n                                </a>\n                            </form>\n                                <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/star\" class=\"js-toggler-form unstarred js-star-button\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" data-remote=\"true\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"nQnqpsGUUYVDCSka1tYn2QpcwUBYoqFTCehYIBwHWhcW9+tWTg+gBXa/spd+Hhfe2xNjXBfz7iTXZpHy4+ksEg==\" /></div>\n                                <button\n                                        class=\"btn btn-sm btn-with-count js-toggler-target\"\n                                        aria-label=\"Star this repository\" title=\"Star code4craft/webmagic\"\n                                        data-ga-click=\"Repository, click star button, action:files#disambiguate; text:Star\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-star\"></span>\n                                    Star\n                                </button>\n                                <a class=\"social-count js-social-count\" href=\"/code4craft/webmagic/stargazers\">\n                                    1,743\n                                </a>\n                            </form>  </div>\n\n                        </li>\n\n                        <li>\n                            <a href=\"#fork-destination-box\" class=\"btn btn-sm btn-with-count\"\n                               title=\"Fork your own copy of code4craft/webmagic to your account\"\n                               aria-label=\"Fork your own copy of code4craft/webmagic to your account\"\n                               rel=\"facebox\"\n                               data-ga-click=\"Repository, show fork modal, action:files#disambiguate; text:Fork\">\n                                <span aria-hidden=\"true\" class=\"octicon octicon-repo-forked\"></span>\n                                Fork\n                            </a>\n\n                            <div id=\"fork-destination-box\" style=\"display: none;\">\n                                <h2 class=\"facebox-header\" data-facebox-id=\"facebox-header\">Where should we fork this repository?</h2>\n                                <include-fragment src=\"\"\n                                                  class=\"js-fork-select-fragment fork-select-fragment\"\n                                                  data-url=\"/code4craft/webmagic/fork?fragment=1\">\n                                    <img alt=\"Loading\" height=\"64\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-128.gif\" width=\"64\" />\n                                </include-fragment>\n                            </div>\n\n                            <a href=\"/code4craft/webmagic/network\" class=\"social-count\">\n                                1,128\n                            </a>\n                        </li>\n                    </ul>\n\n                    <h1 itemscope itemtype=\"http://data-vocabulary.org/Breadcrumb\" class=\"entry-title public \">\n                        <span aria-hidden=\"true\" class=\"octicon octicon-repo\"></span>\n                        <span class=\"author\"><a href=\"/code4craft\" class=\"url fn\" itemprop=\"url\" rel=\"author\"><span itemprop=\"title\">code4craft</span></a></span><!--\n--><span class=\"path-divider\">/</span><!--\n--><strong><a href=\"/code4craft/webmagic\" data-pjax=\"#js-repo-pjax-container\">webmagic</a></strong>\n\n  <span class=\"page-context-loader\">\n    <img alt=\"\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n  </span>\n\n                    </h1>\n\n                </div>\n                <div class=\"container\">\n\n                    <nav class=\"reponav js-repo-nav js-sidenav-container-pjax js-octicon-loaders\"\n                         role=\"navigation\"\n                         data-pjax=\"#js-repo-pjax-container\">\n\n                        <a href=\"/code4craft/webmagic\" aria-label=\"Code\" aria-selected=\"true\" class=\"js-selected-navigation-item selected reponav-item\" data-hotkey=\"g c\" data-selected-links=\"repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /code4craft/webmagic\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-code\"></span>\n                            Code\n                        </a>\n                        <a href=\"/code4craft/webmagic/issues\" class=\"js-selected-navigation-item reponav-item\" data-hotkey=\"g i\" data-selected-links=\"repo_issues repo_labels repo_milestones /code4craft/webmagic/issues\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-issue-opened\"></span>\n                            Issues\n                            <span class=\"counter\">67</span>\n                        </a>\n                        <a href=\"/code4craft/webmagic/pulls\" class=\"js-selected-navigation-item reponav-item\" data-hotkey=\"g p\" data-selected-links=\"repo_pulls /code4craft/webmagic/pulls\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-git-pull-request\"></span>\n                            Pull requests\n                            <span class=\"counter\">14</span>\n                        </a>\n                        <a href=\"/code4craft/webmagic/wiki\" class=\"js-selected-navigation-item reponav-item\" data-hotkey=\"g w\" data-selected-links=\"repo_wiki /code4craft/webmagic/wiki\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-book\"></span>\n                            Wiki\n                        </a>\n                        <a href=\"/code4craft/webmagic/pulse\" class=\"js-selected-navigation-item reponav-item\" data-selected-links=\"pulse /code4craft/webmagic/pulse\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-pulse\"></span>\n                            Pulse\n                        </a>\n                        <a href=\"/code4craft/webmagic/graphs\" class=\"js-selected-navigation-item reponav-item\" data-selected-links=\"repo_graphs repo_contributors /code4craft/webmagic/graphs\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-graph\"></span>\n                            Graphs\n                        </a>\n                        <a href=\"/code4craft/webmagic/settings\" class=\"js-selected-navigation-item reponav-item\" data-selected-links=\"repo_settings repo_branch_settings hooks /code4craft/webmagic/settings\">\n                            <span aria-hidden=\"true\" class=\"octicon octicon-gear\"></span>\n                            Settings\n                        </a>\n                    </nav>\n\n                </div>\n            </div>\n\n            <div class=\"container new-discussion-timeline experiment-repo-nav\">\n                <div class=\"repository-content\">\n\n\n                    <div class=\"repository-meta js-details-container\">\n  <span class=\"repository-meta-content\">\n        A scalable web crawler framework.\n        <a href=\"http://webmagic.io/\" rel=\"nofollow\">http://webmagic.io/</a>\n  </span>\n\n                        <span class=\"edit-link js-details-target\">— <a href=\"#\" class=\"muted-link\">Edit</a></span>\n                        <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/settings/update_meta\" class=\"edit-repository-meta\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"_method\" type=\"hidden\" value=\"put\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"7xX6fGJkjyARqJhxbtYg5AK+hzEpZLP8qatQsSBLDA39GuvJkVwzO80SeWX37wxYpvr1bIudI8ojlj1p5I1zvw==\" /></div>\n\n                        <div class=\"field\">\n                            <label for=\"repo_description\">Description</label>\n                            <input type=\"text\" id=\"repo_description\" class=\"input-contrast repo-description-field\" name=\"repo_description\" value=\"A scalable web crawler framework.\" placeholder=\"Short description of this repository\">\n                        </div>\n\n                        <div class=\"field\" >\n                            <label for=\"repo_homepage\">Website</label>\n                            <input type=\"url\" id=\"repo_homepage\" class=\"input-contrast repo-website-field\" name=\"repo_homepage\" value=\"http://webmagic.io/\" placeholder=\"Website for this repository (optional)\">\n                        </div>\n\n                        <button class=\"btn\">Save</button>\n                        or <a href=\"#\" class=\"js-details-target\">Cancel</a>\n                    </form></div>\n\n\n                    <div class=\"overall-summary overall-summary-bottomless\">\n\n                        <div class=\"stats-switcher-viewport js-stats-switcher-viewport\">\n                            <div class=\"stats-switcher-wrapper\">\n                                <ul class=\"numbers-summary\">\n                                    <li class=\"commits\">\n                                        <a data-pjax href=\"/code4craft/webmagic/commits/master\">\n                                            <span aria-hidden=\"true\" class=\"octicon octicon-history\"></span>\n            <span class=\"num text-emphasized\">\n              698\n            </span>\n                                            commits\n                                        </a>\n                                    </li>\n                                    <li>\n                                        <a data-pjax href=\"/code4craft/webmagic/branches\">\n                                            <span aria-hidden=\"true\" class=\"octicon octicon-git-branch\"></span>\n          <span class=\"num text-emphasized\">\n            6\n          </span>\n                                            branches\n                                        </a>\n                                    </li>\n\n                                    <li>\n                                        <a data-pjax href=\"/code4craft/webmagic/releases\">\n                                            <span aria-hidden=\"true\" class=\"octicon octicon-tag\"></span>\n          <span class=\"num text-emphasized\">\n            13\n          </span>\n                                            releases\n                                        </a>\n                                    </li>\n\n                                    <li>\n\n                                        <a href=\"/code4craft/webmagic/graphs/contributors\">\n                                            <span aria-hidden=\"true\" class=\"octicon octicon-organization\"></span>\n    <span class=\"num text-emphasized\">\n      23\n    </span>\n                                            contributors\n                                        </a>\n                                    </li>\n                                </ul>\n\n                                <div class=\"repository-lang-stats\">\n                                    <ol class=\"repository-lang-stats-numbers\">\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=java\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#b07219;\"></span>\n                                                <span class=\"lang\">Java</span>\n                                                <span class=\"percent\">72.2%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=css\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#563d7c;\"></span>\n                                                <span class=\"lang\">CSS</span>\n                                                <span class=\"percent\">11.6%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=javascript\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#f1e05a;\"></span>\n                                                <span class=\"lang\">JavaScript</span>\n                                                <span class=\"percent\">8.5%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=freemarker\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#0050b2;\"></span>\n                                                <span class=\"lang\">FreeMarker</span>\n                                                <span class=\"percent\">7.4%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=html\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#e44b23;\"></span>\n                                                <span class=\"lang\">HTML</span>\n                                                <span class=\"percent\">0.2%</span>\n                                            </a>\n                                        </li>\n                                        <li>\n                                            <a href=\"/code4craft/webmagic/search?l=ruby\">\n                                                <span class=\"color-block language-color\" style=\"background-color:#701516;\"></span>\n                                                <span class=\"lang\">Ruby</span>\n                                                <span class=\"percent\">0.1%</span>\n                                            </a>\n                                        </li>\n                                    </ol>\n                                </div>\n                            </div>\n                        </div>\n\n                    </div>\n\n                    <div class=\"repository-lang-stats-graph js-toggle-lang-stats\" title=\"Click for language details\">\n                        <span class=\"language-color\" aria-label=\"Java 72.2%\" style=\"width:72.2%; background-color:#b07219;\" itemprop=\"keywords\">Java</span>\n                        <span class=\"language-color\" aria-label=\"CSS 11.6%\" style=\"width:11.6%; background-color:#563d7c;\" itemprop=\"keywords\">CSS</span>\n                        <span class=\"language-color\" aria-label=\"JavaScript 8.5%\" style=\"width:8.5%; background-color:#f1e05a;\" itemprop=\"keywords\">JavaScript</span>\n                        <span class=\"language-color\" aria-label=\"FreeMarker 7.4%\" style=\"width:7.4%; background-color:#0050b2;\" itemprop=\"keywords\">FreeMarker</span>\n                        <span class=\"language-color\" aria-label=\"HTML 0.2%\" style=\"width:0.2%; background-color:#e44b23;\" itemprop=\"keywords\">HTML</span>\n                        <span class=\"language-color\" aria-label=\"Ruby 0.1%\" style=\"width:0.1%; background-color:#701516;\" itemprop=\"keywords\">Ruby</span>\n                    </div>\n\n                    <include-fragment src=\"/code4craft/webmagic/show_partial?partial=tree%2Frecently_touched_branches_list\"></include-fragment>\n\n                    <div class=\"file-navigation in-mid-page file-navigation-new\">\n                        <div class=\"right\">\n                            <div class=\"btn-group\">\n\n                                <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/new/master\" class=\"button_to js-new-blob-form\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"XOKyr9wZjCR+NGJTatrBJTz6EfVIx0qK42atG8cU8mGVCvihIi+04Zb0Y916iB+cmvs9fIDiC+Gg45gG6Y1inw==\" /></div>\n                                <button class=\"btn btn-sm tooltipped tooltipped-n js-new-blob-submit\" type=\"submit\"\n                                        data-disable-with=\"working…\" aria-label=\"Create a new file here\">\n                                    New file\n                                </button>\n                            </form>\n\n\n                                <a href=\"/code4craft/webmagic/find/master\"\n                                   class=\"btn btn-sm empty-icon right js-show-file-finder\"\n                                   data-pjax\n                                   data-hotkey=\"t\"\n                                   data-ga-click=\"Repository, find file, location:repo overview\">\n                                    Find file\n                                </a>\n                            </div>\n                            <div class=\"file-navigation-options\" data-multiple>\n\n                                <div class=\"file-navigation-option\">\n                                    <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/users/set_protocol\" class=\"js-set-user-protocol-preference\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" data-remote=\"true\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"Sx794jiPAE0pdEIUNJhp4AUyhkPwdamIAAKBQQGDtNe+0e8whjFgMrGl63/fDAEmggpzui33hAJ0GQ0EEYf/Rw==\" /></div>\n                                    <input type=\"hidden\" name=\"protocol_type\" value=\"push\">\n\n                                    <div class=\"select-menu js-menu-container js-select-menu\">\n                                        <div class=\"input-group js-select-button js-zeroclipboard-container\">\n                                            <div class=\"input-group-button\">\n                                                <button type=\"button\" class=\"btn btn-sm select-menu-button js-menu-target\" data-ga-click=\"Repository, clone SSH, location:repo overview\">\n                                                    SSH\n                                                </button>\n                                            </div>\n                                            <input type=\"text\" class=\"input-monospace input-mini js-zeroclipboard-target js-url-field\" value=\"git@github.com:code4craft/webmagic.git\" readonly>\n                                            <div class=\"input-group-button\">\n                                                <button aria-label=\"Copy to clipboard\" class=\"js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s\" data-copied-hint=\"Copied!\" type=\"button\"><span aria-hidden=\"true\" class=\"octicon octicon-clippy\"></span></button>\n                                            </div>\n\n                                        </div>\n\n                                        <div class=\"select-menu-modal-holder\">\n                                            <div class=\"select-menu-modal js-menu-content\" aria-hidden=\"true\">\n                                                <div class=\"select-menu-header\">\n                                                    <span aria-label=\"Close\" class=\"octicon octicon-x js-menu-close\" role=\"button\"></span>\n                                                    <span class=\"select-menu-title\">Choose a clone URL</span>\n                                                </div>\n\n                                                <div class=\"select-menu-list js-navigation-container\" role=\"menu\">\n                                                    <div class=\"select-menu-item js-navigation-item \" role=\"menuitem\" tabindex=\"0\">\n                                                        <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                        <div class=\"select-menu-item-text\">\n                                                            <input type=\"radio\" name=\"protocol_selector\" value=\"http\" >\n                          <span class=\"select-menu-item-heading\">\n                            HTTPS\n                            (recommended)\n                          </span>\n                            <span class=\"description\">\n                              Clone with Git or checkout with SVN using the repository's web address.\n                            </span>\n                          <span class=\"js-select-button-text hidden-select-button-text\">\n                            <div class=\"input-group-button\">\n                                <button type=\"button\" class=\"btn btn-sm select-menu-button js-menu-target\" data-ga-click=\"Repository, clone HTTPS, location:repo overview\">\n                                    HTTPS\n                                </button>\n                            </div>\n<input type=\"text\" class=\"input-monospace input-mini js-zeroclipboard-target js-url-field\" value=\"https://github.com/code4craft/webmagic.git\" readonly>\n<div class=\"input-group-button\">\n    <button aria-label=\"Copy to clipboard\" class=\"js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s\" data-copied-hint=\"Copied!\" type=\"button\"><span aria-hidden=\"true\" class=\"octicon octicon-clippy\"></span></button>\n</div>\n\n                          </span>\n                                                        </div>\n                                                    </div>\n                                                    <div class=\"select-menu-item js-navigation-item selected\" role=\"menuitem\" tabindex=\"0\">\n                                                        <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n                                                        <div class=\"select-menu-item-text\">\n                                                            <input type=\"radio\" name=\"protocol_selector\" value=\"ssh\" checked>\n                          <span class=\"select-menu-item-heading\">\n                            SSH\n\n                          </span>\n                            <span class=\"description\">\n                              Clone with an SSH key and passphrase from your GitHub settings.\n                            </span>\n                          <span class=\"js-select-button-text hidden-select-button-text\">\n                            <div class=\"input-group-button\">\n                                <button type=\"button\" class=\"btn btn-sm select-menu-button js-menu-target\" data-ga-click=\"Repository, clone SSH, location:repo overview\">\n                                    SSH\n                                </button>\n                            </div>\n<input type=\"text\" class=\"input-monospace input-mini js-zeroclipboard-target js-url-field\" value=\"git@github.com:code4craft/webmagic.git\" readonly>\n<div class=\"input-group-button\">\n    <button aria-label=\"Copy to clipboard\" class=\"js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s\" data-copied-hint=\"Copied!\" type=\"button\"><span aria-hidden=\"true\" class=\"octicon octicon-clippy\"></span></button>\n</div>\n\n                          </span>\n                                                        </div>\n                                                    </div>\n                                                </div>\n                                                <div class=\"select-menu-list\" role=\"menu\">\n                                                    <a class=\"select-menu-item select-menu-action\" href=\"https://help.github.com/articles/which-remote-url-should-i-use\" target=\"_blank\">\n                                                        <span aria-hidden=\"true\" class=\"octicon octicon-question select-menu-item-icon\"></span>\n                                                        <div class=\"select-menu-item-text\">\n                                                            Learn more about clone URLs\n                                                        </div>\n                                                    </a>\n                                                </div>\n                                            </div>\n                                        </div>\n                                    </div>\n                                </form>        </div>\n\n                                <div class=\"file-navigation-option\">\n                                    <a href=\"github-mac://openRepo/https://github.com/code4craft/webmagic\" class=\"btn btn-sm tooltipped tooltipped-s tooltipped-multiline\" aria-label=\"Save code4craft/webmagic to your computer and use it in GitHub Desktop.\">\n                                        <span aria-hidden=\"true\" class=\"octicon octicon-desktop-download\"></span>\n                                    </a>\n                                </div>\n\n\n                                <div class=\"file-navigation-option\">\n                                    <a href=\"/code4craft/webmagic/archive/master.zip\"\n                                       class=\"btn btn-sm\"\n                                       rel=\"nofollow\"\n                                       data-ga-click=\"Repository, download zip, location:repo overview\">\n                                        Download ZIP\n                                    </a>\n                                </div>\n                            </div>\n                        </div>\n\n\n                        <div class=\"select-menu js-menu-container js-select-menu left\">\n                            <button class=\"btn btn-sm select-menu-button js-menu-target css-truncate\" data-hotkey=\"w\"\n                                    title=\"master\"\n                                    type=\"button\" aria-label=\"Switch branches or tags\" tabindex=\"0\" aria-haspopup=\"true\">\n                                <i>Branch:</i>\n                                <span class=\"js-select-button css-truncate-target\">master</span>\n                            </button>\n\n                            <div class=\"select-menu-modal-holder js-menu-content js-navigation-container\" data-pjax aria-hidden=\"true\">\n\n                                <div class=\"select-menu-modal\">\n                                    <div class=\"select-menu-header\">\n                                        <span aria-label=\"Close\" class=\"octicon octicon-x js-menu-close\" role=\"button\"></span>\n                                        <span class=\"select-menu-title\">Switch branches/tags</span>\n                                    </div>\n\n                                    <div class=\"select-menu-filters\">\n                                        <div class=\"select-menu-text-filter\">\n                                            <input type=\"text\" aria-label=\"Find or create a branch…\" id=\"context-commitish-filter-field\" class=\"js-filterable-field js-navigation-enable\" placeholder=\"Find or create a branch…\">\n                                        </div>\n                                        <div class=\"select-menu-tabs\">\n                                            <ul>\n                                                <li class=\"select-menu-tab\">\n                                                    <a href=\"#\" data-tab-filter=\"branches\" data-filter-placeholder=\"Find or create a branch…\" class=\"js-select-menu-tab\" role=\"tab\">Branches</a>\n                                                </li>\n                                                <li class=\"select-menu-tab\">\n                                                    <a href=\"#\" data-tab-filter=\"tags\" data-filter-placeholder=\"Find a tag…\" class=\"js-select-menu-tab\" role=\"tab\">Tags</a>\n                                                </li>\n                                            </ul>\n                                        </div>\n                                    </div>\n\n                                    <div class=\"select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket\" data-tab-filter=\"branches\" role=\"menu\">\n\n                                        <div data-filterable-for=\"context-commitish-filter-field\" data-filterable-type=\"substring\">\n\n\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/0.4.x\"\n                                               data-name=\"0.4.x\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"0.4.x\">\n                0.4.x\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/0.6.0\"\n                                               data-name=\"0.6.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"0.6.0\">\n                0.6.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/en-webmagic\"\n                                               data-name=\"en-webmagic\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"en-webmagic\">\n                en-webmagic\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/gh-pages\"\n                                               data-name=\"gh-pages\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"gh-pages\">\n                gh-pages\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open selected\"\n                                               href=\"/code4craft/webmagic/tree/master\"\n                                               data-name=\"master\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"master\">\n                master\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/stable\"\n                                               data-name=\"stable\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"stable\">\n                stable\n              </span>\n                                            </a>\n                                        </div>\n\n                                        <!-- </textarea> --><!-- '\"` --><form accept-charset=\"UTF-8\" action=\"/code4craft/webmagic/branches\" class=\"js-create-branch select-menu-item select-menu-new-item-form js-navigation-item js-new-item-form\" data-form-nonce=\"3b3b1453e901b97918f8e2a9efa6ed4efb295cf6\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /><input name=\"authenticity_token\" type=\"hidden\" value=\"TFV2kT/IcGmiqdH0NqRYxcNkepWIxxCkgnxla0/LxJMYaWluy1/I4QYo83JwZFB5WnNJPxF7S+BqjspGMqGmwA==\" /></div>\n                                        <span aria-hidden=\"true\" class=\"octicon octicon-git-branch select-menu-item-icon\"></span>\n                                        <div class=\"select-menu-item-text\">\n                                            <span class=\"select-menu-item-heading\">Create branch: <span class=\"js-new-item-name\"></span></span>\n                                            <span class=\"description\">from ‘master’</span>\n                                        </div>\n                                        <input type=\"hidden\" name=\"name\" id=\"name\" class=\"js-new-item-value\">\n                                        <input type=\"hidden\" name=\"branch\" id=\"branch\" value=\"master\">\n                                        <input type=\"hidden\" name=\"path\" id=\"path\" value=\"\">\n                                    </form>\n                                    </div>\n\n                                    <div class=\"select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket\" data-tab-filter=\"tags\">\n                                        <div data-filterable-for=\"context-commitish-filter-field\" data-filterable-type=\"substring\">\n\n\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmaigc-0.4.3\"\n                                               data-name=\"webmaigc-0.4.3\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmaigc-0.4.3\">\n                webmaigc-0.4.3\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-parent-0.3.1\"\n                                               data-name=\"webmagic-parent-0.3.1\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-parent-0.3.1\">\n                webmagic-parent-0.3.1\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-parent-0.2.1\"\n                                               data-name=\"webmagic-parent-0.2.1\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-parent-0.2.1\">\n                webmagic-parent-0.2.1\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.4.2\"\n                                               data-name=\"webmagic-0.4.2\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.4.2\">\n                webmagic-0.4.2\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.4.1\"\n                                               data-name=\"webmagic-0.4.1\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.4.1\">\n                webmagic-0.4.1\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.4.0\"\n                                               data-name=\"webmagic-0.4.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.4.0\">\n                webmagic-0.4.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.3.2\"\n                                               data-name=\"webmagic-0.3.2\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.3.2\">\n                webmagic-0.3.2\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/webmagic-0.3.0\"\n                                               data-name=\"webmagic-0.3.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"webmagic-0.3.0\">\n                webmagic-0.3.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/version-0.2.0\"\n                                               data-name=\"version-0.2.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"version-0.2.0\">\n                version-0.2.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/version-0.1.0\"\n                                               data-name=\"version-0.1.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"version-0.1.0\">\n                version-0.1.0\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/WebMagic-0.5.2\"\n                                               data-name=\"WebMagic-0.5.2\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"WebMagic-0.5.2\">\n                WebMagic-0.5.2\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/WebMagic-0.5.1\"\n                                               data-name=\"WebMagic-0.5.1\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"WebMagic-0.5.1\">\n                WebMagic-0.5.1\n              </span>\n                                            </a>\n                                            <a class=\"select-menu-item js-navigation-item js-navigation-open \"\n                                               href=\"/code4craft/webmagic/tree/WebMagic-0.5.0\"\n                                               data-name=\"WebMagic-0.5.0\"\n                                               data-skip-pjax=\"true\"\n                                               rel=\"nofollow\">\n                                                <span aria-hidden=\"true\" class=\"octicon octicon-check select-menu-item-icon\"></span>\n              <span class=\"select-menu-item-text css-truncate-target\" title=\"WebMagic-0.5.0\">\n                WebMagic-0.5.0\n              </span>\n                                            </a>\n                                        </div>\n\n                                        <div class=\"select-menu-no-results\">Nothing to show</div>\n                                    </div>\n\n                                </div>\n                            </div>\n                        </div>\n\n\n                        <a href=\"/code4craft/webmagic/pull/new/master\" class=\"btn btn-sm btn-primary\" data-pjax data-ga-click=\"Repository, new pull request, location:repo overview\">\n                            New pull request\n                        </a>\n\n                        <div class=\"breadcrumb\">\n\n                        </div>\n                    </div>\n\n\n\n\n                    <div class=\"commit-tease js-details-container\">\n    <span class=\"right\">\n      Latest commit\n      <a class=\"commit-tease-sha\" href=\"/code4craft/webmagic/commit/800f66c4cc7e1e4b3e485af5236e3c9b8d54f028\" data-pjax>\n          800f66c\n      </a>\n      <time datetime=\"2016-01-18T15:20:08Z\" is=\"relative-time\">Jan 18, 2016</time>\n    </span>\n\n\n    <span class=\"commit-author-section\">\n      <img alt=\"@code4craft\" class=\"avatar\" height=\"20\" src=\"https://avatars2.githubusercontent.com/u/1351884?v=3&amp;s=40\" width=\"20\" />\n      <a href=\"/code4craft\" class=\"user-mention\" rel=\"author\">code4craft</a>\n    </span>\n\n                        <a href=\"/code4craft/webmagic/commit/800f66c4cc7e1e4b3e485af5236e3c9b8d54f028\" class=\"message\" data-pjax=\"true\" title=\"Revert &quot;remove some unkown config&quot;\n\nThis reverts commit 0e245c989605c94b8daa21be8da9ac7002c10568.\">Revert \"remove some unkown config\"</a>\n          <span class=\"hidden-text-expander inline\">\n            <a href=\"#\" class=\"js-details-target\">…</a>\n          </span>\n                        </span>\n\n                        <div class=\"commit-desc\"><pre class=\"text-small\">This reverts commit <a href=\"https://github.com/code4craft/webmagic/commit/0e245c989605c94b8daa21be8da9ac7002c10568\" class=\"commit-link\"><tt>0e245c9</tt></a>.</pre></div>\n                    </div>\n\n\n                    <div class=\"file-wrap \">\n\n                        <a href=\"/code4craft/webmagic/tree/800f66c4cc7e1e4b3e485af5236e3c9b8d54f028\" class=\"hidden js-permalink-shortcut\" data-hotkey=\"y\">Permalink</a>\n\n                        <table class=\"files js-navigation-container js-active-navigation-container\" data-pjax>\n\n\n                            <tbody>\n                            <tr class=\"warning include-fragment-error\">\n                                <td class=\"icon\"><span aria-hidden=\"true\" class=\"octicon octicon-alert\"></span></td>\n                                <td class=\"content\" colspan=\"3\">Failed to load latest commit information.</td>\n                            </tr>\n\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/assets\" class=\"js-directory-link js-navigation-open\" id=\"32bb636196f91ed59d7a49190e26b42c-3bc5c153572a8e40990cf593b34139cba724f15c\" title=\"assets\">assets</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/644e8d1f72c08c83348e5c31a42f0f0dfa32f07d\" class=\"message\" data-pjax=\"true\" title=\"同步官方源码\">同步官方源码</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-04-12T14:32:22Z\" is=\"time-ago\">Apr 12, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/en_docs\" class=\"js-directory-link js-navigation-open\" id=\"025516923597c2d7f987828ad6657c14-d80a6b0dee9c88e6b198bc58b3cb0704b3ce07c4\" title=\"en_docs\">en_docs</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/dbebcbe44f07acb8871a0e3f786dd3d10d938a1c\" class=\"message\" data-pjax=\"true\" title=\"docs\">docs</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-05-02T22:14:31Z\" is=\"time-ago\">May 3, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-avalon\" class=\"js-directory-link js-navigation-open\" id=\"079d784782a58fecda2d64e6fadff4ca-c2dff4951c408dd117233ed6a57daa4b7cda0473\" title=\"webmagic-avalon\">webmagic-avalon</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/7668731f08a3118390e7651002d56b2223d4e656\" class=\"message\" data-pjax=\"true\" title=\"update version to snapshot\">update version to snapshot</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-05-04T23:03:55Z\" is=\"time-ago\">May 5, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-core\" class=\"js-directory-link js-navigation-open\" id=\"39809e13bc65c3873f79570b81852d62-a2cf4af3f59391cccb922597dd0c4819a3426667\" title=\"webmagic-core\">webmagic-core</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/90e14b31b0c229d5664092ea01f739f264e419a8\" class=\"message\" data-pjax=\"true\" title=\"修正FileCacheQueueScheduler导致程序不能正常结束和未关闭流\n\nFileCacheQueueScheduler中开启了一个线程周期运行来保存数据但在爬虫结束后没有关闭导致程序无法结束，以及没有关闭io流。\n\n解决方法：\n让FileCacheQueueScheduler实现Closable接口，在close方法中关闭线程以及流。\n在Spider的close方法中添加对scheduler的关闭操作。\">修正FileCacheQueueScheduler导致程序不能正常结束和未关闭流</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2015-11-12T15:10:20Z\" is=\"time-ago\">Nov 12, 2015</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-extension\" class=\"js-directory-link js-navigation-open\" id=\"dc82c79bcb262e1942088502bb426876-35467ae616c037bd947e6752a20167d5fb74d3b5\" title=\"webmagic-extension\">webmagic-extension</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/cfde3b7657d208a80625b61b430bef11889ecc0e\" class=\"message\" data-pjax=\"true\" title=\"Merge pull request #237 from SpenceZhou/master\n\nUpdate RedisScheduler.java\">Merge pull request</a> <a href=\"https://github.com/code4craft/webmagic/pull/237\" class=\"issue-link js-issue-link\" data-url=\"https://github.com/code4craft/webmagic/issues/237\" data-id=\"119897705\" data-error-text=\"Failed to load issue title\" data-permission-text=\"Issue title is private\">#237</a> <a href=\"/code4craft/webmagic/commit/cfde3b7657d208a80625b61b430bef11889ecc0e\" class=\"message\" data-pjax=\"true\" title=\"Merge pull request #237 from SpenceZhou/master\n\nUpdate RedisScheduler.java\">from SpenceZhou/master</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2015-12-02T14:17:00Z\" is=\"time-ago\">Dec 2, 2015</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-samples\" class=\"js-directory-link js-navigation-open\" id=\"4284b70d4c5e11003fb292b0d0f7539f-264e0e2eafe7960dcd72844100faa1460fad5cfb\" title=\"webmagic-samples\">webmagic-samples</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/84b046e4c962841b725cb1be6165f40c549e2ef8\" class=\"message\" data-pjax=\"true\" title=\"Merge pull request #227 from hsqlu/master\n\nupdate deprecated method\">Merge pull request</a> <a href=\"https://github.com/code4craft/webmagic/pull/227\" class=\"issue-link js-issue-link\" data-url=\"https://github.com/code4craft/webmagic/issues/227\" data-id=\"107109677\" data-error-text=\"Failed to load issue title\" data-permission-text=\"Issue title is private\">#227</a> <a href=\"/code4craft/webmagic/commit/84b046e4c962841b725cb1be6165f40c549e2ef8\" class=\"message\" data-pjax=\"true\" title=\"Merge pull request #227 from hsqlu/master\n\nupdate deprecated method\">from hsqlu/master</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2016-01-16T11:36:52Z\" is=\"time-ago\">Jan 16, 2016</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-saxon\" class=\"js-directory-link js-navigation-open\" id=\"5ee0de5b970664e15f6805d957403c63-8311a46ae76f5669f4be3da0e2a01cce327caf97\" title=\"webmagic-saxon\">webmagic-saxon</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5f8c3fd5c518099b7028369fc35df4c01065f42e\" class=\"message\" data-pjax=\"true\" title=\"update version\">update version</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-06-04T09:33:30Z\" is=\"time-ago\">Jun 4, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-scripts\" class=\"js-directory-link js-navigation-open\" id=\"8ecc7fcb462c06097aa24a7048097d3d-0422570614304398e2739f4d5e13c12ee403add9\" title=\"webmagic-scripts\">webmagic-scripts</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5f8c3fd5c518099b7028369fc35df4c01065f42e\" class=\"message\" data-pjax=\"true\" title=\"update version\">update version</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-06-04T09:33:30Z\" is=\"time-ago\">Jun 4, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/webmagic-selenium\" class=\"js-directory-link js-navigation-open\" id=\"988c197af393f3198711cebacce7fd65-455315f3cbd4108203da09a88afd566d65d161e1\" title=\"webmagic-selenium\">webmagic-selenium</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5d365f7bf46f854d2e05dc31a066cd6c37994fab\" class=\"message\" data-pjax=\"true\" title=\"update and validate pom.xml\n\nUpdate selenium and GhostDriver (PhantomJSDriver) to latest version.\">update and validate pom.xml</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2015-07-11T14:43:49Z\" is=\"time-ago\">Jul 11, 2015</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-directory\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/tree/master/zh_docs\" class=\"js-directory-link js-navigation-open\" id=\"bec3b859688b0bbdb94899b1a5b56441-e305b1e0799520204fb6aca537fa5a922240329a\" title=\"zh_docs\">zh_docs</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/2a15bc028962e650463db331794f2b515a77880a\" class=\"message\" data-pjax=\"true\" title=\"contributor\">contributor</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-06-04T14:27:16Z\" is=\"time-ago\">Jun 4, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/.gitignore\" class=\"js-directory-link js-navigation-open\" id=\"a084b794bc0759e7a6b77810e01874f2-0175dbaadc0ab38c5b79ca4a0944fb63b4f8973c\" title=\".gitignore\">.gitignore</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/07ea04223f419d3eb4f3e68c2b69391c93283454\" class=\"message\" data-pjax=\"true\" title=\"change_gitignore\">change_gitignore</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-05-19T07:56:22Z\" is=\"time-ago\">May 19, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/.travis.yml\" class=\"js-directory-link js-navigation-open\" id=\"354f30a63fb0907d4ad57269548329e3-a9f233f37f99ae2dcd5aa2cfefe18738158dd470\" title=\".travis.yml\">.travis.yml</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/73ae7a1d52253bd097283b62a7152f22ffadb60d\" class=\"message\" data-pjax=\"true\" title=\"remove ci for jdk6\">remove ci for jdk6</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2016-01-18T15:19:39Z\" is=\"time-ago\">Jan 18, 2016</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/README.md\" class=\"js-directory-link js-navigation-open\" id=\"04c6e90faac2675aa89e2176d2eec7d8-98fea5a59788254b208d7f2752baf2d77a029dca\" title=\"README.md\">README.md</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5e8ca02ec670e18f52361296072929fc0a93efc3\" class=\"message\" data-pjax=\"true\" title=\"contributor\">contributor</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-06-04T14:26:56Z\" is=\"time-ago\">Jun 4, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/pom.xml\" class=\"js-directory-link js-navigation-open\" id=\"600376dffeb79835ede4a0b285078036-e7290bc95daf3ae60b8ace743d5c822e99223be5\" title=\"pom.xml\">pom.xml</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/800f66c4cc7e1e4b3e485af5236e3c9b8d54f028\" class=\"message\" data-pjax=\"true\" title=\"Revert &quot;remove some unkown config&quot;\n\nThis reverts commit 0e245c989605c94b8daa21be8da9ac7002c10568.\">Revert \"remove some unkown config\"</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2016-01-18T15:20:08Z\" is=\"time-ago\">Jan 18, 2016</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/release-note.md\" class=\"js-directory-link js-navigation-open\" id=\"d59c2d5d8d04d144da5f1cd251c384ad-f44704efd075006a4fc3935fb6607b158f3815b4\" title=\"release-note.md\">release-note.md</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"https://github.com/code4craft/webmagic/issues/34\" class=\"issue-link js-issue-link\" data-url=\"https://github.com/code4craft/webmagic/issues/34\" data-id=\"22319882\" data-error-text=\"Failed to load issue title\" data-permission-text=\"Issue title is private\">#34</a> <a href=\"/code4craft/webmagic/commit/b838c4e4331326e38e7c30c56d39be9d71fc930a\" class=\"message\" data-pjax=\"true\" title=\"#34 Close reader in FileCacheQueueScheduler\">Close reader in FileCacheQueueScheduler</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2013-11-08T06:59:09Z\" is=\"time-ago\">Nov 8, 2013</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/user-manual.md\" class=\"js-directory-link js-navigation-open\" id=\"a5d0f6c7ea51007118aea16b56f50a6a-17f65291cbb26141ec6f27422918d8da7f6b8755\" title=\"user-manual.md\">user-manual.md</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/5f6f48931497d80463dace8a97e66e9a7b10d79e\" class=\"message\" data-pjax=\"true\" title=\"deperate in user manual\">deperate in user manual</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2014-05-02T22:29:37Z\" is=\"time-ago\">May 3, 2014</time></span>\n                                </td>\n                            </tr>\n                            <tr class=\"js-navigation-item\">\n                                <td class=\"icon\">\n                                    <span aria-hidden=\"true\" class=\"octicon octicon-file-text\"></span>\n                                    <img alt=\"\" class=\"spinner\" height=\"16\" src=\"https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif\" width=\"16\" />\n                                </td>\n                                <td class=\"content\">\n                                    <span class=\"css-truncate css-truncate-target\"><a href=\"/code4craft/webmagic/blob/master/webmagic-avalon.md\" class=\"js-directory-link js-navigation-open\" id=\"5fbef994bb80a792d34444969fa7f80c-bcf39ea065c240dd3bbbbb758ada151d2f1e025c\" title=\"webmagic-avalon.md\">webmagic-avalon.md</a></span>\n                                </td>\n                                <td class=\"message\">\n            <span class=\"css-truncate css-truncate-target\">\n                  <a href=\"/code4craft/webmagic/commit/7c43b5146e6eb8c309c3a6cdfd58bda70ab932ec\" class=\"message\" data-pjax=\"true\" title=\"scripts readme\">scripts readme</a>\n            </span>\n                                </td>\n                                <td class=\"age\">\n                                    <span class=\"css-truncate css-truncate-target\"><time datetime=\"2013-11-28T04:04:05Z\" is=\"time-ago\">Nov 28, 2013</time></span>\n                                </td>\n                            </tr>\n                            </tbody>\n                        </table>\n\n                    </div>\n\n\n\n                    <div id=\"readme\" class=\"boxed-group clearfix announce instapaper_body md\">\n                        <h3>\n                            <span aria-hidden=\"true\" class=\"octicon octicon-book\"></span>\n                            README.md\n                        </h3>\n\n                        <article class=\"markdown-body entry-content\" itemprop=\"mainContentOfPage\"><p><a href=\"https://camo.githubusercontent.com/77fe3da40f9b2c5839df0267890a2457a64003e0/68747470733a2f2f7261772e6769746875622e636f6d2f636f64653463726166742f7765626d616769632f6d61737465722f6173736574732f6c6f676f2e6a7067\" target=\"_blank\"><img src=\"https://camo.githubusercontent.com/77fe3da40f9b2c5839df0267890a2457a64003e0/68747470733a2f2f7261772e6769746875622e636f6d2f636f64653463726166742f7765626d616769632f6d61737465722f6173736574732f6c6f676f2e6a7067\" alt=\"logo\" data-canonical-src=\"https://raw.github.com/code4craft/webmagic/master/assets/logo.jpg\" style=\"max-width:100%;\"></a></p>\n\n                            <p><a href=\"https://github.com/code4craft/webmagic/tree/master/zh_docs\">Readme in Chinese</a></p>\n\n                            <p><a href=\"https://github.com/code4craft/webmagic/blob/master/user-manual.md\">User Manual (Chinese)</a></p>\n\n                            <p><a href=\"https://travis-ci.org/code4craft/webmagic\"><img src=\"https://camo.githubusercontent.com/28f799aaf9175c6e3b3c131896651cf1775b2bc8/68747470733a2f2f7472617669732d63692e6f72672f636f64653463726166742f7765626d616769632e706e673f6272616e63683d6d6173746572\" alt=\"Build Status\" data-canonical-src=\"https://travis-ci.org/code4craft/webmagic.png?branch=master\" style=\"max-width:100%;\"></a></p>\n\n                            <blockquote>\n                                <p>A scalable crawler framework. It covers the whole lifecycle of crawler: downloading, url management, content extraction and persistent. It can simplify the development of a  specific crawler.</p>\n                            </blockquote>\n\n                            <h2><a id=\"user-content-features\" class=\"anchor\" href=\"#features\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Features:</h2>\n\n                            <ul>\n                                <li>Simple core with high flexibility.</li>\n                                <li>Simple API for html extracting.</li>\n                                <li>Annotation with POJO to customize a crawler, no configuration.</li>\n                                <li>Multi-thread and Distribution support.</li>\n                                <li>Easy to be integrated.</li>\n                            </ul>\n\n                            <h2><a id=\"user-content-install\" class=\"anchor\" href=\"#install\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Install:</h2>\n\n                            <p>Add dependencies to your pom.xml:</p>\n\n                            <div class=\"highlight highlight-text-xml\"><pre>&lt;<span class=\"pl-ent\">dependency</span>&gt;\n    &lt;<span class=\"pl-ent\">groupId</span>&gt;us.codecraft&lt;/<span class=\"pl-ent\">groupId</span>&gt;\n    &lt;<span class=\"pl-ent\">artifactId</span>&gt;webmagic-core&lt;/<span class=\"pl-ent\">artifactId</span>&gt;\n    &lt;<span class=\"pl-ent\">version</span>&gt;0.5.2&lt;/<span class=\"pl-ent\">version</span>&gt;\n&lt;/<span class=\"pl-ent\">dependency</span>&gt;\n&lt;<span class=\"pl-ent\">dependency</span>&gt;\n    &lt;<span class=\"pl-ent\">groupId</span>&gt;us.codecraft&lt;/<span class=\"pl-ent\">groupId</span>&gt;\n    &lt;<span class=\"pl-ent\">artifactId</span>&gt;webmagic-extension&lt;/<span class=\"pl-ent\">artifactId</span>&gt;\n    &lt;<span class=\"pl-ent\">version</span>&gt;0.5.2&lt;/<span class=\"pl-ent\">version</span>&gt;\n&lt;/<span class=\"pl-ent\">dependency</span>&gt;</pre></div>\n\n                            <p>WebMagic use slf4j with slf4j-log4j12 implementation. If you customized your slf4j implementation, please exclude slf4j-log4j12.</p>\n\n                            <div class=\"highlight highlight-text-xml\"><pre>&lt;<span class=\"pl-ent\">exclusions</span>&gt;\n    &lt;<span class=\"pl-ent\">exclusion</span>&gt;\n        &lt;<span class=\"pl-ent\">groupId</span>&gt;org.slf4j&lt;/<span class=\"pl-ent\">groupId</span>&gt;\n        &lt;<span class=\"pl-ent\">artifactId</span>&gt;slf4j-log4j12&lt;/<span class=\"pl-ent\">artifactId</span>&gt;\n    &lt;/<span class=\"pl-ent\">exclusion</span>&gt;\n&lt;/<span class=\"pl-ent\">exclusions</span>&gt;</pre></div>\n\n                            <h2><a id=\"user-content-get-started\" class=\"anchor\" href=\"#get-started\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Get Started:</h2>\n\n                            <h3><a id=\"user-content-first-crawler\" class=\"anchor\" href=\"#first-crawler\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>First crawler:</h3>\n\n                            <p>Write a class implements PageProcessor. For example, I wrote a crawler of github repository infomation.</p>\n\n                            <div class=\"highlight highlight-source-java\"><pre><span class=\"pl-k\">public</span> <span class=\"pl-k\">class</span> <span class=\"pl-en\">GithubRepoPageProcessor</span> <span class=\"pl-k\">implements</span> <span class=\"pl-e\">PageProcessor</span> {\n\n    <span class=\"pl-k\">private</span> <span class=\"pl-smi\">Site</span> site <span class=\"pl-k\">=</span> <span class=\"pl-smi\">Site</span><span class=\"pl-k\">.</span>me()<span class=\"pl-k\">.</span>setRetryTimes(<span class=\"pl-c1\">3</span>)<span class=\"pl-k\">.</span>setSleepTime(<span class=\"pl-c1\">1000</span>);\n\n    <span class=\"pl-k\">@Override</span>\n    <span class=\"pl-k\">public</span> <span class=\"pl-k\">void</span> <span class=\"pl-en\">process</span>(<span class=\"pl-smi\">Page</span> <span class=\"pl-v\">page</span>) {\n        page<span class=\"pl-k\">.</span>addTargetRequests(page<span class=\"pl-k\">.</span>getHtml()<span class=\"pl-k\">.</span>links()<span class=\"pl-k\">.</span>regex(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>(https://github<span class=\"pl-cce\">\\\\</span>.com/<span class=\"pl-cce\">\\\\</span>w+/<span class=\"pl-cce\">\\\\</span>w+)<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>all());\n        page<span class=\"pl-k\">.</span>putField(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>author<span class=\"pl-pds\">\"</span></span>, page<span class=\"pl-k\">.</span>getUrl()<span class=\"pl-k\">.</span>regex(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github<span class=\"pl-cce\">\\\\</span>.com/(<span class=\"pl-cce\">\\\\</span>w+)/.*<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>toString());\n        page<span class=\"pl-k\">.</span>putField(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>name<span class=\"pl-pds\">\"</span></span>, page<span class=\"pl-k\">.</span>getHtml()<span class=\"pl-k\">.</span>xpath(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>//h1[@class='entry-title public']/strong/a/text()<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>toString());\n        <span class=\"pl-k\">if</span> (page<span class=\"pl-k\">.</span>getResultItems()<span class=\"pl-k\">.</span>get(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>name<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">==</span><span class=\"pl-c1\">null</span>){\n            <span class=\"pl-c\">//skip this page</span>\n            page<span class=\"pl-k\">.</span>setSkip(<span class=\"pl-c1\">true</span>);\n        }\n        page<span class=\"pl-k\">.</span>putField(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>readme<span class=\"pl-pds\">\"</span></span>, page<span class=\"pl-k\">.</span>getHtml()<span class=\"pl-k\">.</span>xpath(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>//div[@id='readme']/tidyText()<span class=\"pl-pds\">\"</span></span>));\n    }\n\n    <span class=\"pl-k\">@Override</span>\n    <span class=\"pl-k\">public</span> <span class=\"pl-smi\">Site</span> <span class=\"pl-en\">getSite</span>() {\n        <span class=\"pl-k\">return</span> site;\n    }\n\n    <span class=\"pl-k\">public</span> <span class=\"pl-k\">static</span> <span class=\"pl-k\">void</span> <span class=\"pl-en\">main</span>(<span class=\"pl-k\">String</span>[] <span class=\"pl-v\">args</span>) {\n        <span class=\"pl-smi\">Spider</span><span class=\"pl-k\">.</span>create(<span class=\"pl-k\">new</span> <span class=\"pl-smi\">GithubRepoPageProcessor</span>())<span class=\"pl-k\">.</span>addUrl(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github.com/code4craft<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>thread(<span class=\"pl-c1\">5</span>)<span class=\"pl-k\">.</span>run();\n    }\n}</pre></div>\n\n                            <ul>\n                                <li><p><code>page.addTargetRequests(links)</code></p>\n\n                                    <p>Add urls for crawling.</p></li>\n                            </ul>\n\n                            <p>You can also use annotation way:</p>\n\n                            <div class=\"highlight highlight-source-java\"><pre>@TargetUrl(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github.com/<span class=\"pl-cce\">\\\\</span>w+/<span class=\"pl-cce\">\\\\</span>w+<span class=\"pl-pds\">\"</span></span>)\n@HelpUrl(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github.com/<span class=\"pl-cce\">\\\\</span>w+<span class=\"pl-pds\">\"</span></span>)\n<span class=\"pl-k\">public</span> <span class=\"pl-k\">class</span> <span class=\"pl-en\">GithubRepo</span> {\n\n    <span class=\"pl-k\">@ExtractBy</span>(<span class=\"pl-c1\">value</span> <span class=\"pl-k\">=</span> <span class=\"pl-s\"><span class=\"pl-pds\">\"</span>//h1[@class='entry-title public']/strong/a/text()<span class=\"pl-pds\">\"</span></span>, <span class=\"pl-c1\">notNull</span> <span class=\"pl-k\">=</span> <span class=\"pl-c1\">true</span>)\n    <span class=\"pl-k\">private</span> <span class=\"pl-smi\">String</span> name;\n\n    <span class=\"pl-k\">@ExtractByUrl</span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github<span class=\"pl-cce\">\\\\</span>.com/(<span class=\"pl-cce\">\\\\</span>w+)/.*<span class=\"pl-pds\">\"</span></span>)\n    <span class=\"pl-k\">private</span> <span class=\"pl-smi\">String</span> author;\n\n    <span class=\"pl-k\">@ExtractBy</span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>//div[@id='readme']/tidyText()<span class=\"pl-pds\">\"</span></span>)\n    <span class=\"pl-k\">private</span> <span class=\"pl-smi\">String</span> readme;\n\n    <span class=\"pl-k\">public</span> <span class=\"pl-k\">static</span> <span class=\"pl-k\">void</span> <span class=\"pl-en\">main</span>(<span class=\"pl-k\">String</span>[] <span class=\"pl-v\">args</span>) {\n        <span class=\"pl-smi\">OOSpider</span><span class=\"pl-k\">.</span>create(<span class=\"pl-smi\">Site</span><span class=\"pl-k\">.</span>me()<span class=\"pl-k\">.</span>setSleepTime(<span class=\"pl-c1\">1000</span>)\n                , <span class=\"pl-k\">new</span> <span class=\"pl-smi\">ConsolePageModelPipeline</span>(), <span class=\"pl-smi\">GithubRepo</span><span class=\"pl-k\">.</span>class)\n                .addUrl(<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>https://github.com/code4craft<span class=\"pl-pds\">\"</span></span>)<span class=\"pl-k\">.</span>thread(<span class=\"pl-c1\">5</span>)<span class=\"pl-k\">.</span>run();\n    }\n}</pre></div>\n\n                            <h3><a id=\"user-content-docs-and-samples\" class=\"anchor\" href=\"#docs-and-samples\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Docs and samples:</h3>\n\n                            <p>Documents: <a href=\"http://webmagic.io/docs/\">http://webmagic.io/docs/</a></p>\n\n                            <p>The architecture of webmagic (refered to <a href=\"http://scrapy.org/\">Scrapy</a>)</p>\n\n                            <p><a href=\"https://camo.githubusercontent.com/06cb8227231a6adf6d2a57b14b60a25389a25fe9/687474703a2f2f636f64653463726166742e6769746875622e696f2f696d616765732f706f7374732f7765626d616769632e706e67\" target=\"_blank\"><img src=\"https://camo.githubusercontent.com/06cb8227231a6adf6d2a57b14b60a25389a25fe9/687474703a2f2f636f64653463726166742e6769746875622e696f2f696d616765732f706f7374732f7765626d616769632e706e67\" alt=\"image\" data-canonical-src=\"http://code4craft.github.io/images/posts/webmagic.png\" style=\"max-width:100%;\"></a></p>\n\n                            <p>Javadocs: <a href=\"http://code4craft.github.io/webmagic/docs/en/\">http://code4craft.github.io/webmagic/docs/en/</a></p>\n\n                            <p>There are some samples in <code>webmagic-samples</code> package.</p>\n\n                            <h3><a id=\"user-content-lisence\" class=\"anchor\" href=\"#lisence\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Lisence:</h3>\n\n                            <p>Lisenced under <a href=\"http://opensource.org/licenses/Apache-2.0\">Apache 2.0 lisence</a></p>\n\n                            <h3><a id=\"user-content-contributors\" class=\"anchor\" href=\"#contributors\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Contributors:</h3>\n\n                            <p>Thanks these people for commiting source code, reporting bugs or suggesting for new feature:</p>\n\n                            <ul>\n                                <li><a href=\"https://github.com/ccliangbo\">ccliangbo</a></li>\n                                <li><a href=\"https://github.com/yuany\">yuany</a></li>\n                                <li><a href=\"https://github.com/yxssfxwzy\">yxssfxwzy</a></li>\n                                <li><a href=\"https://github.com/linkerlin\">linkerlin</a></li>\n                                <li><a href=\"https://github.com/d0ngw\">d0ngw</a></li>\n                                <li><a href=\"https://github.com/xuchaoo\">xuchaoo</a></li>\n                                <li><a href=\"https://github.com/supermicah\">supermicah</a></li>\n                                <li><a href=\"https://github.com/SimpleExpress\">SimpleExpress</a></li>\n                                <li><a href=\"https://github.com/aruanruan\">aruanruan</a></li>\n                                <li><a href=\"https://github.com/l1z2g9\">l1z2g9</a></li>\n                                <li><a href=\"https://github.com/zhegexiaohuozi\">zhegexiaohuozi</a></li>\n                                <li><a href=\"https://github.com/ywooer\">ywooer</a></li>\n                                <li><a href=\"https://github.com/yyw258520\">yyw258520</a></li>\n                                <li><a href=\"https://github.com/perfecking\">perfecking</a></li>\n                                <li><a href=\"http://my.oschina.net/lidongyang\">lidongyang</a></li>\n                                <li><a href=\"https://github.com/seveniu\">seveniu</a></li>\n                                <li><a href=\"https://github.com/sebastian1118\">sebastian1118</a></li>\n                                <li><a href=\"https://github.com/codev777\">codev777</a></li>\n                                <li><a href=\"https://github.com/fengwuze\">fengwuze</a></li>\n                            </ul>\n\n                            <h3><a id=\"user-content-thanks\" class=\"anchor\" href=\"#thanks\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Thanks:</h3>\n\n                            <p>To write webmagic, I refered to the projects below :</p>\n\n                            <ul>\n                                <li><p><strong>Scrapy</strong></p>\n\n                                    <p>A crawler framework in Python.</p>\n\n                                    <p><a href=\"http://scrapy.org/\">http://scrapy.org/</a></p></li>\n                                <li><p><strong>Spiderman</strong></p>\n\n                                    <p>Another crawler framework in Java.</p>\n\n                                    <p><a href=\"https://gitcafe.com/laiweiwei/Spiderman\">https://gitcafe.com/laiweiwei/Spiderman</a></p></li>\n                            </ul>\n\n                            <h3><a id=\"user-content-mail-list\" class=\"anchor\" href=\"#mail-list\" aria-hidden=\"true\"><span class=\"octicon octicon-link\"></span></a>Mail-list:</h3>\n\n                            <p><a href=\"https://groups.google.com/forum/#!forum/webmagic-java\">https://groups.google.com/forum/#!forum/webmagic-java</a></p>\n\n                            <p><a href=\"http://list.qq.com/cgi-bin/qf_invite?id=023a01f505246785f77c5a5a9aff4e57ab20fcdde871e988\">http://list.qq.com/cgi-bin/qf_invite?id=023a01f505246785f77c5a5a9aff4e57ab20fcdde871e988</a></p>\n\n                            <p>QQ Group: 373225642</p>\n\n                            <p><a href=\"https://bitdeli.com/free\" title=\"Bitdeli Badge\"><img src=\"https://camo.githubusercontent.com/ac3c3cde05f612ce1a1c9a8be3bf2893ffa6d64d/68747470733a2f2f64327765637a68766c38323376302e636c6f756466726f6e742e6e65742f636f64653463726166742f7765626d616769632f7472656e642e706e67\" alt=\"Bitdeli Badge\" data-canonical-src=\"https://d2weczhvl823v0.cloudfront.net/code4craft/webmagic/trend.png\" style=\"max-width:100%;\"></a></p>\n                        </article>\n                    </div>\n\n\n                </div>\n                <div class=\"modal-backdrop\"></div>\n            </div>\n\n        </div>\n    </div>\n\n</div>\n\n<div class=\"container\">\n    <div class=\"site-footer\" role=\"contentinfo\">\n        <ul class=\"site-footer-links right\">\n            <li><a href=\"https://status.github.com/\" data-ga-click=\"Footer, go to status, text:status\">Status</a></li>\n            <li><a href=\"https://developer.github.com\" data-ga-click=\"Footer, go to api, text:api\">API</a></li>\n            <li><a href=\"https://training.github.com\" data-ga-click=\"Footer, go to training, text:training\">Training</a></li>\n            <li><a href=\"https://shop.github.com\" data-ga-click=\"Footer, go to shop, text:shop\">Shop</a></li>\n            <li><a href=\"https://github.com/blog\" data-ga-click=\"Footer, go to blog, text:blog\">Blog</a></li>\n            <li><a href=\"https://github.com/about\" data-ga-click=\"Footer, go to about, text:about\">About</a></li>\n            <li><a href=\"https://github.com/pricing\" data-ga-click=\"Footer, go to pricing, text:pricing\">Pricing</a></li>\n\n        </ul>\n\n        <a href=\"https://github.com\" aria-label=\"Homepage\">\n            <span aria-hidden=\"true\" class=\"mega-octicon octicon-mark-github\" title=\"GitHub \"></span>\n        </a>\n        <ul class=\"site-footer-links\">\n            <li>&copy; 2016 <span title=\"0.16501s from github-fe119-cp1-prd.iad.github.net\">GitHub</span>, Inc.</li>\n            <li><a href=\"https://github.com/site/terms\" data-ga-click=\"Footer, go to terms, text:terms\">Terms</a></li>\n            <li><a href=\"https://github.com/site/privacy\" data-ga-click=\"Footer, go to privacy, text:privacy\">Privacy</a></li>\n            <li><a href=\"https://github.com/security\" data-ga-click=\"Footer, go to security, text:security\">Security</a></li>\n            <li><a href=\"https://github.com/contact\" data-ga-click=\"Footer, go to contact, text:contact\">Contact</a></li>\n            <li><a href=\"https://help.github.com\" data-ga-click=\"Footer, go to help, text:help\">Help</a></li>\n        </ul>\n    </div>\n</div>\n\n\n\n\n\n\n\n<div id=\"ajax-error-message\" class=\"flash flash-error\">\n    <span aria-hidden=\"true\" class=\"octicon octicon-alert\"></span>\n    <button type=\"button\" class=\"flash-close js-flash-close js-ajax-error-dismiss\" aria-label=\"Dismiss error\">\n        <span aria-hidden=\"true\" class=\"octicon octicon-x\"></span>\n    </button>\n    Something went wrong with that request. Please try again.\n</div>\n\n\n<script crossorigin=\"anonymous\" src=\"https://assets-cdn.github.com/assets/frameworks-2895475c714f13790b63e636b5389a6918a260259c5b22a15acf5ef26bd6ef09.js\"></script>\n<script async=\"async\" crossorigin=\"anonymous\" src=\"https://assets-cdn.github.com/assets/github-c0404608a3bcd1310776df0ab26e107bfd70ff0382408f43ede1a81e730e39cd.js\"></script>\n\n\n\n<div class=\"js-stale-session-flash stale-session-flash flash flash-warn flash-banner hidden\">\n    <span aria-hidden=\"true\" class=\"octicon octicon-alert\"></span>\n    <span class=\"signed-in-tab-flash\">You signed in with another tab or window. <a href=\"\">Reload</a> to refresh your session.</span>\n    <span class=\"signed-out-tab-flash\">You signed out in another tab or window. <a href=\"\">Reload</a> to refresh your session.</span>\n</div>\n<div class=\"facebox\" id=\"facebox\" style=\"display:none;\">\n    <div class=\"facebox-popup\">\n        <div class=\"facebox-content\" role=\"dialog\" aria-labelledby=\"facebox-header\" aria-describedby=\"facebox-description\">\n        </div>\n        <button type=\"button\" class=\"facebox-close js-facebox-close\" aria-label=\"Close modal\">\n            <span aria-hidden=\"true\" class=\"octicon octicon-x\"></span>\n        </button>\n    </div>\n</div>\n\n</body>\n</html>\n\n"
  },
  {
    "path": "webmagic-extension/src/test/resources/html/mock-webmagic.html",
    "content": "<!DOCTYPE html>\n<html>\n<head lang=\"zh\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<div class=\"date\">20170603</div>\n<div class=\"number\">12</div>\n<ul>\n    <li class=\"list\"><a href=\"http://webmagic.io/list/1\"></a></li>\n    <li class=\"list\"><a href=\"http://webmagic.io/list/2\"></a></li>\n    <li class=\"list\"><a href=\"http://webmagic.io/list/3\"></a></li>\n    <li class=\"list\"><a href=\"http://webmagic.io/list/4\"></a></li>\n</ul>\n<ul>\n    <li class=\"post\"><a href=\"http://webmagic.io/post/1\"></a></li>\n    <li class=\"post\"><a href=\"http://webmagic.io/post/2\"></a></li>\n    <li class=\"post\"><a href=\"http://webmagic.io/post/3\"></a></li>\n    <li class=\"post\"><a href=\"http://webmagic.io/post/4\"></a></li>\n</ul>\n<ul>\n    <li class=\"foo\"><a href=\"http://webmagic.io/foo/1\"></a></li>\n    <li class=\"foo\"><a href=\"http://webmagic.io/foo/2\"></a></li>\n    <li class=\"foo\"><a href=\"http://webmagic.io/bar/3\"></a></li>\n    <li class=\"foo\"><a href=\"http://webmagic.io/bar/4\"></a></li>\n</ul>\n<ul>\n    <li class=\"bar\"><a href=\"http://webmagic.io/bar/1\"></a></li>\n    <li class=\"bar\"><a href=\"http://webmagic.io/bar/2\"></a></li>\n    <li class=\"bar\"><a href=\"http://webmagic.io/foo/3\"></a></li>\n    <li class=\"bar\"><a href=\"http://webmagic.io/foo/4\"></a></li>\n</ul>\n\n<ul>\n    <li class=\"numbers\">1</li>\n    <li class=\"numbers\">2</li>\n    <li class=\"numbers\">3</li>\n    <li class=\"numbers\">4</li>\n</ul>\n<ul>\n    <li class=\"dates\">20170601</li>\n    <li class=\"dates\">20170602</li>\n    <li class=\"dates\">20170603</li>\n    <li class=\"dates\">20170604</li>\n</ul>\n</body>\n</html>"
  },
  {
    "path": "webmagic-extension/src/test/resources/json/mock-githubrepo.json",
    "content": "{\n  \"id\": 9623064,\n  \"name\": \"webmagic\",\n  \"full_name\": \"code4craft/webmagic\",\n  \"owner\": {\n    \"login\": \"code4craft\",\n    \"id\": 1351884,\n    \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1351884?v=3\",\n    \"gravatar_id\": \"\",\n    \"url\": \"https://api.github.com/users/code4craft\",\n    \"html_url\": \"https://github.com/code4craft\",\n    \"followers_url\": \"https://api.github.com/users/code4craft/followers\",\n    \"following_url\": \"https://api.github.com/users/code4craft/following{/other_user}\",\n    \"gists_url\": \"https://api.github.com/users/code4craft/gists{/gist_id}\",\n    \"starred_url\": \"https://api.github.com/users/code4craft/starred{/owner}{/repo}\",\n    \"subscriptions_url\": \"https://api.github.com/users/code4craft/subscriptions\",\n    \"organizations_url\": \"https://api.github.com/users/code4craft/orgs\",\n    \"repos_url\": \"https://api.github.com/users/code4craft/repos\",\n    \"events_url\": \"https://api.github.com/users/code4craft/events{/privacy}\",\n    \"received_events_url\": \"https://api.github.com/users/code4craft/received_events\",\n    \"type\": \"User\",\n    \"site_admin\": false\n  },\n  \"private\": false,\n  \"html_url\": \"https://github.com/code4craft/webmagic\",\n  \"description\": \"A scalable web crawler framework for Java.\",\n  \"fork\": false,\n  \"url\": \"https://api.github.com/repos/code4craft/webmagic\",\n  \"forks_url\": \"https://api.github.com/repos/code4craft/webmagic/forks\",\n  \"keys_url\": \"https://api.github.com/repos/code4craft/webmagic/keys{/key_id}\",\n  \"collaborators_url\": \"https://api.github.com/repos/code4craft/webmagic/collaborators{/collaborator}\",\n  \"teams_url\": \"https://api.github.com/repos/code4craft/webmagic/teams\",\n  \"hooks_url\": \"https://api.github.com/repos/code4craft/webmagic/hooks\",\n  \"issue_events_url\": \"https://api.github.com/repos/code4craft/webmagic/issues/events{/number}\",\n  \"events_url\": \"https://api.github.com/repos/code4craft/webmagic/events\",\n  \"assignees_url\": \"https://api.github.com/repos/code4craft/webmagic/assignees{/user}\",\n  \"branches_url\": \"https://api.github.com/repos/code4craft/webmagic/branches{/branch}\",\n  \"tags_url\": \"https://api.github.com/repos/code4craft/webmagic/tags\",\n  \"blobs_url\": \"https://api.github.com/repos/code4craft/webmagic/git/blobs{/sha}\",\n  \"git_tags_url\": \"https://api.github.com/repos/code4craft/webmagic/git/tags{/sha}\",\n  \"git_refs_url\": \"https://api.github.com/repos/code4craft/webmagic/git/refs{/sha}\",\n  \"trees_url\": \"https://api.github.com/repos/code4craft/webmagic/git/trees{/sha}\",\n  \"statuses_url\": \"https://api.github.com/repos/code4craft/webmagic/statuses/{sha}\",\n  \"languages_url\": \"https://api.github.com/repos/code4craft/webmagic/languages\",\n  \"stargazers_url\": \"https://api.github.com/repos/code4craft/webmagic/stargazers\",\n  \"contributors_url\": \"https://api.github.com/repos/code4craft/webmagic/contributors\",\n  \"subscribers_url\": \"https://api.github.com/repos/code4craft/webmagic/subscribers\",\n  \"subscription_url\": \"https://api.github.com/repos/code4craft/webmagic/subscription\",\n  \"commits_url\": \"https://api.github.com/repos/code4craft/webmagic/commits{/sha}\",\n  \"git_commits_url\": \"https://api.github.com/repos/code4craft/webmagic/git/commits{/sha}\",\n  \"comments_url\": \"https://api.github.com/repos/code4craft/webmagic/comments{/number}\",\n  \"issue_comment_url\": \"https://api.github.com/repos/code4craft/webmagic/issues/comments{/number}\",\n  \"contents_url\": \"https://api.github.com/repos/code4craft/webmagic/contents/{+path}\",\n  \"compare_url\": \"https://api.github.com/repos/code4craft/webmagic/compare/{base}...{head}\",\n  \"merges_url\": \"https://api.github.com/repos/code4craft/webmagic/merges\",\n  \"archive_url\": \"https://api.github.com/repos/code4craft/webmagic/{archive_format}{/ref}\",\n  \"downloads_url\": \"https://api.github.com/repos/code4craft/webmagic/downloads\",\n  \"issues_url\": \"https://api.github.com/repos/code4craft/webmagic/issues{/number}\",\n  \"pulls_url\": \"https://api.github.com/repos/code4craft/webmagic/pulls{/number}\",\n  \"milestones_url\": \"https://api.github.com/repos/code4craft/webmagic/milestones{/number}\",\n  \"notifications_url\": \"https://api.github.com/repos/code4craft/webmagic/notifications{?since,all,participating}\",\n  \"labels_url\": \"https://api.github.com/repos/code4craft/webmagic/labels{/name}\",\n  \"releases_url\": \"https://api.github.com/repos/code4craft/webmagic/releases{/id}\",\n  \"deployments_url\": \"https://api.github.com/repos/code4craft/webmagic/deployments\",\n  \"created_at\": \"2013-04-23T12:57:36Z\",\n  \"updated_at\": \"2017-06-03T03:58:13Z\",\n  \"pushed_at\": \"2017-06-03T07:10:15Z\",\n  \"git_url\": \"git://github.com/code4craft/webmagic.git\",\n  \"ssh_url\": \"git@github.com:code4craft/webmagic.git\",\n  \"clone_url\": \"https://github.com/code4craft/webmagic.git\",\n  \"svn_url\": \"https://github.com/code4craft/webmagic\",\n  \"homepage\": \"http://webmagic.io/\",\n  \"size\": 16982,\n  \"stargazers_count\": 4566,\n  \"watchers_count\": 4566,\n  \"language\": \"Java\",\n  \"has_issues\": true,\n  \"has_projects\": true,\n  \"has_downloads\": true,\n  \"has_wiki\": true,\n  \"has_pages\": true,\n  \"forks_count\": 2432,\n  \"mirror_url\": null,\n  \"open_issues_count\": 96,\n  \"forks\": 2432,\n  \"open_issues\": 96,\n  \"watchers\": 4566,\n  \"default_branch\": \"master\",\n  \"network_count\": 2432,\n  \"subscribers_count\": 618\n}\n"
  },
  {
    "path": "webmagic-extension/src/test/resources/log4j2-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration>\n    <Appenders>\n        <Console name=\"stdout\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"%d{yy-MM-dd HH:mm:ss,SSS} %-5p %c(%F:%L) ## %m%n\" />\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Logger name=\"org.apache\" level=\"warn\" additivity=\"false\">\n            <AppenderRef ref=\"stdout\" />\n        </Logger>\n        <Root level=\"info\">\n            <AppenderRef ref=\"stdout\" />\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "webmagic-samples/README.md",
    "content": "webmagic-samples\n-------\nwebmagic的一些示例。包括抓取常见 博客、信息类网站等。"
  },
  {
    "path": "webmagic-samples/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n    xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://maven.apache.org/POM/4.0.0\n        http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>us.codecraft</groupId>\n        <artifactId>webmagic</artifactId>\n        <version>1.0.4-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>webmagic-samples</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-core</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-extension</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.mapdb</groupId>\n            <artifactId>mapdb</artifactId>\n            <version>3.1.0</version>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n            <version>2.15.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-annotations</artifactId>\n            <version>2.15.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>2.16.0</version>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/main/QuickStarter.java",
    "content": "package us.codecraft.webmagic.main;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.samples.IteyeBlog;\nimport us.codecraft.webmagic.model.samples.News163;\nimport us.codecraft.webmagic.model.samples.OschinaBlog;\nimport us.codecraft.webmagic.pipeline.ConsolePipeline;\nimport us.codecraft.webmagic.pipeline.MultiPagePipeline;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Scanner;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class QuickStarter {\n\n    private static Map<String, Class> clazzMap;\n\n    private static Map<String, String> urlMap;\n\n    private static void init(){\n        clazzMap = new LinkedHashMap<String, Class>();\n        clazzMap.put(\"1\", OschinaBlog.class);\n        clazzMap.put(\"2\", IteyeBlog.class);\n        clazzMap.put(\"3\", News163.class);\n        urlMap = new LinkedHashMap<String, String>();\n        urlMap.put(\"1\", \"http://my.oschina.net/flashsword/blog\");\n        urlMap.put(\"2\", \"http://flashsword20.iteye.com/\");\n        urlMap.put(\"3\", \"http://news.163.com/\");\n    }\n\n    public static void main(String[] args) {\n        init();\n        String key = null;\n        key = readKey(key);\n        System.out.println(\"The demo started and will last 20 seconds...\");\n        //Start spider\n        OOSpider.create(Site.me(), clazzMap.get(key)).addUrl(urlMap.get(key)).addPipeline(new MultiPagePipeline()).addPipeline(new ConsolePipeline()).runAsync();\n\n        try {\n            Thread.sleep(20000);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n        System.out.println(\"The demo stopped!\");\n        System.out.println(\"To more usage, try to customize your own Spider!\");\n        System.exit(0);\n    }\n\n    private static String readKey(String key) {\n        Scanner stdin = new Scanner(System.in);\n        System.out.println(\"Choose a Spider demo:\");\n        for (Map.Entry<String, Class> classEntry : clazzMap.entrySet()) {\n            System.out.println(classEntry.getKey()+\"\\t\" + classEntry.getValue() + \"\\t\" + urlMap.get(classEntry.getKey()));\n        }\n        while (key == null) {\n            key = stdin.nextLine();\n            if (clazzMap.get(key) == null) {\n                System.out.println(\"Invalid choice!\");\n                key = null;\n            }\n        }\n        return key;\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/BaiduNews.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class BaiduNews {\n\n    @ExtractBy(\"//h3[@class='c-title']/a/text()\")\n    private String name;\n\n    @ExtractBy(\"//div[@class='c-summary']/text()\")\n    private String description;\n\n    @Override\n    public String toString() {\n        return \"BaiduNews{\" +\n                \"name='\" + name + '\\'' +\n                \", description='\" + description + '\\'' +\n                '}';\n    }\n\n    public static void main(String[] args) {\n        OOSpider ooSpider = OOSpider.create(Site.me().setSleepTime(0), BaiduNews.class);\n        //single download\n        BaiduNews baike = ooSpider.<BaiduNews>get(\"http://news.baidu.com/ns?tn=news&cl=2&rn=20&ct=1&fr=bks0000&ie=utf-8&word=httpclient\");\n        System.out.println(baike);\n\n        ooSpider.close();\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n}"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/Blog.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-8-2 <br>\n * Time: 上午8:10 <br>\n */\npublic interface Blog {\n\n    public String getTitle();\n\n    public String getContent();\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/DianpingFtlDataScanner.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.AfterExtractor;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\n\nimport java.util.List;\n\n/**\n * @author yihua.huang@dianping.com <br>\n *         Date: 13-8-13 <br>\n *         Time: 上午10:13 <br>\n */\n@TargetUrl(\"http://*.alpha.dp/*\")\npublic class DianpingFtlDataScanner implements AfterExtractor {\n\n\t@ExtractBy(value = \"(DP\\\\.data\\\\(\\\\{.*\\\\}\\\\));\", type = ExtractBy.Type.Regex, notNull = true, multi = true)\n\tprivate List<String> data;\n\n\tpublic static void main(String[] args) {\n\t\tOOSpider.create(Site.me().setSleepTime(0), DianpingFtlDataScanner.class)\n\t\t\t\t.thread(5).run();\n\t}\n\n\t@Override\n\tpublic void afterProcess(Page page) {\n\t\tif (data.size() > 1) {\n\t\t\tSystem.err.println(page.getUrl());\n\t\t}\n\t\tif (data.size() > 0 && data.get(0).length() > 100) {\n\t\t\tSystem.err.println(page.getUrl());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/GithubRepo.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.HasKey;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.ExtractByUrl;\nimport us.codecraft.webmagic.model.annotation.HelpUrl;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\nimport us.codecraft.webmagic.pipeline.JsonFilePageModelPipeline;\nimport us.codecraft.webmagic.scheduler.FileCacheQueueScheduler;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\n@TargetUrl(\"https://github.com/\\\\w+/\\\\w+\")\n@HelpUrl({\"https://github.com/\\\\w+\\\\?tab=repositories\",\"https://github.com/\\\\w+\",\"https://github.com/explore/*\"})\npublic class GithubRepo implements HasKey {\n\n    @ExtractBy(value = \"//h1[@class='entry-title public']/strong/a/text()\", notNull = true)\n    private String name;\n\n    @ExtractByUrl(\"https://github\\\\.com/(\\\\w+)/.*\")\n    private String author;\n\n    @ExtractBy(\"//div[@id='readme']\")\n    private String readme;\n\n    @ExtractBy(value = \"//div[@class='repository-lang-stats']//li//span[@class='lang']\",multi = true)\n    private List<String> language;\n\n    @ExtractBy(\"//a[@class='social-count js-social-count']/text()\")\n    private String star;\n\n    @ExtractBy(\"//a[@class='social-count js-social-count']/text()\")\n    private String fork;\n\n    @ExtractByUrl\n    private String url;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me().setSleepTime(0).setRetryTimes(3),\n                new JsonFilePageModelPipeline(), GithubRepo.class)\n                .addUrl(\"https://github.com/explore\")\n                .setScheduler(new FileCacheQueueScheduler(\"/data/webmagic/cache/\")).thread(15).run();\n    }\n\n    @Override\n    public String key() {\n        return author+\"_\"+name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getReadme() {\n        return readme;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public List<String> getLanguage() {\n        return language;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public String getStar() {\n        return star;\n    }\n\n    public String getFork() {\n        return fork;\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/IteyeBlog.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-8-2 <br>\n * Time: 上午7:52 <br>\n */\n@TargetUrl(\"http://*.iteye.com/blog/*\")\npublic class IteyeBlog implements Blog{\n\n    @ExtractBy(\"//title\")\n    private String title;\n\n    @ExtractBy(value = \"div#blog_content\",type = ExtractBy.Type.Css)\n    private String content;\n\n    @Override\n    public String toString() {\n        return \"IteyeBlog{\" +\n                \"title='\" + title + '\\'' +\n                \", content='\" + content + '\\'' +\n                '}';\n    }\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me(), IteyeBlog.class).addUrl(\"http://flashsword20.iteye.com/blog\").run();\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public String getContent() {\n        return content;\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/JokejiModel.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.ConsolePageModelPipeline;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.HelpUrl;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\nimport us.codecraft.webmagic.scheduler.RedisScheduler;\n\n/**\n * @author code4crafter@gmail.com\n */\n@TargetUrl(\"http://www.jokeji.cn/jokehtml/jy/\\\\d+.htm\")\n@HelpUrl(\"http://www.jokeji.cn/list\\\\w+.htm\")\npublic class JokejiModel {\n\n    @ExtractBy(\"//title/regex('<title>([^_]+)',1)\")\n    private String title;\n\n    @ExtractBy(\"//div[@class=mob_txt]/tidyText()\")\n    private String content;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me().setDomain(\"www.jokeji.cn\").setCharset(\"gbk\").setSleepTime(100).setTimeOut(3000)\n                .setUserAgent(\"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)\")\n                , new ConsolePageModelPipeline(), JokejiModel.class).addUrl(\"http://www.jokeji.cn/\").thread(2)\n                .scheduler(new RedisScheduler(\"127.0.0.1\"))\n                .run();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/Kr36NewsModel.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.monitor.SpiderMonitor;\nimport us.codecraft.webmagic.pipeline.PageModelPipeline;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.ExtractByUrl;\nimport us.codecraft.webmagic.model.annotation.HelpUrl;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\n\nimport javax.management.JMException;\nimport java.io.IOException;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\n@TargetUrl(\"http://www.36kr.com/p/\\\\d+.html\")\n@HelpUrl(\"http://www.36kr.com/#/page/\\\\d+\")\npublic class Kr36NewsModel {\n\n    @ExtractBy(\"//h1[@class='entry-title sep10']\")\n    private String title;\n\n    @ExtractBy(\"//div[@class='mainContent sep-10']/tidyText()\")\n    private String content;\n\n    @ExtractByUrl\n    private String url;\n\n    public static void main(String[] args) throws IOException, JMException {\n        //Just for benchmark\n        Spider thread = OOSpider.create(Site.me().setSleepTime(0), new PageModelPipeline() {\n            @Override\n            public void process(Object o, Task task) {\n\n            }\n        }, Kr36NewsModel.class).thread(20).addUrl(\"http://www.36kr.com/\");\n        thread.start();\n        SpiderMonitor spiderMonitor = SpiderMonitor.instance();\n        spiderMonitor.register(thread);\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public String getContent() {\n        return content;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/News163.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.MultiPageModel;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.ExtractByUrl;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\nimport us.codecraft.webmagic.pipeline.ConsolePipeline;\nimport us.codecraft.webmagic.pipeline.MultiPagePipeline;\nimport us.codecraft.webmagic.scheduler.RedisScheduler;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\n@TargetUrl(\"http://news.163.com/\\\\d+/\\\\d+/\\\\d+/\\\\w+*.html\")\npublic class News163 implements MultiPageModel {\n\n    @ExtractByUrl(\"http://news\\\\.163\\\\.com/\\\\d+/\\\\d+/\\\\d+/([^_]*).*\\\\.html\")\n    private String pageKey;\n\n    @ExtractByUrl(value = \"http://news\\\\.163\\\\.com/\\\\d+/\\\\d+/\\\\d+/\\\\w+_(\\\\d+)\\\\.html\", notNull = false)\n    private String page;\n\n    @ExtractBy(value = \"//div[@class=\\\"ep-pages\\\"]//a/regex('http://news\\\\.163\\\\.com/\\\\d+/\\\\d+/\\\\d+/\\\\w+_(\\\\d+)\\\\.html',1)\"\n            , multi = true, notNull = false)\n    private List<String> otherPage;\n\n    @ExtractBy(\"//h1[@id=\\\"h1title\\\"]/text()\")\n    private String title;\n\n    @ExtractBy(\"//div[@id=\\\"epContentLeft\\\"]\")\n    private String content;\n\n    @Override\n    public String getPageKey() {\n        return pageKey;\n    }\n\n    @Override\n    public Collection<String> getOtherPages() {\n        return otherPage;\n    }\n\n    @Override\n    public String getPage() {\n        if (page == null) {\n            return \"1\";\n        }\n        return page;\n    }\n\n    @Override\n    public MultiPageModel combine(MultiPageModel multiPageModel) {\n        News163 news163 = new News163();\n        news163.title = this.title;\n        News163 pagedModel1 = (News163) multiPageModel;\n        news163.content = this.content + pagedModel1.content;\n        return news163;\n    }\n\n    @Override\n    public String toString() {\n        return \"News163{\" +\n                \"content='\" + content + '\\'' +\n                \", title='\" + title + '\\'' +\n                \", otherPage=\" + otherPage +\n                '}';\n    }\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me(), News163.class).addUrl(\"http://news.163.com/13/0802/05/958I1E330001124J_2.html\")\n                .scheduler(new RedisScheduler(\"localhost\")).addPipeline(new MultiPagePipeline()).addPipeline(new ConsolePipeline()).run();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/OschinaAnswer.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.*;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.HelpUrl;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\n@TargetUrl(\"http://www.oschina.net/question/\\\\d+_\\\\d+*\")\n@HelpUrl(\"http://www.oschina.net/question/*\")\n@ExtractBy(value = \"//ul[@class='list']/li[@class='Answer']\", multi = true)\npublic class OschinaAnswer implements AfterExtractor{\n\n    @ExtractBy(\"//img/@title\")\n    private String user;\n\n    @ExtractBy(\"//div[@class='detail']\")\n    private String content;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me(), OschinaAnswer.class).addUrl(\"http://www.oschina.net/question/567527_120597\").run();\n    }\n\n    @Override\n    public void afterProcess(Page page) {\n\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/OschinaBlog.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.pipeline.PageModelPipeline;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\n@TargetUrl(\"http://my.oschina.net/flashsword/blog/\\\\d+\")\npublic class OschinaBlog{\n\n    @ExtractBy(\"//title\")\n    private String title;\n\n    @ExtractBy(value = \"div.BlogContent\",type = ExtractBy.Type.Css)\n    private String content;\n\n    @ExtractBy(value = \"//div[@class='BlogTags']/a/text()\", multi = true)\n    private List<String> tags;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me()\n                .setUserAgent(\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36\")\n                .setSleepTime(0)\n                .setRetryTimes(3)\n                ,new PageModelPipeline() {\n            @Override\n            public void process(Object o, Task task) {\n\n            }\n        }, OschinaBlog.class).thread(10).addUrl(\"http://my.oschina.net/flashsword/blog\").run();\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public String getContent() {\n        return content;\n    }\n\n    public List<String> getTags() {\n        return tags;\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/model/samples/QQMeishi.java",
    "content": "package us.codecraft.webmagic.model.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.ConsolePageModelPipeline;\nimport us.codecraft.webmagic.model.OOSpider;\nimport us.codecraft.webmagic.model.annotation.ExtractBy;\nimport us.codecraft.webmagic.model.annotation.TargetUrl;\n\n/**\n * @author code4crafter@gmail.com\n */\n@TargetUrl(\"http://meishi.qq.com/beijing/c/all[\\\\-p2]*\")\n@ExtractBy(value = \"//ul[@id=\\\"promos_list2\\\"]/li\",multi = true)\npublic class QQMeishi {\n\n    @ExtractBy(\"//div[@class=info]/a[@class=title]/h4/text()\")\n    private String shopName;\n\n    @ExtractBy(\"//div[@class=info]/a[@class=title]/text()\")\n    private String promo;\n\n    public static void main(String[] args) {\n        OOSpider.create(Site.me(), new ConsolePageModelPipeline(), QQMeishi.class).addUrl(\"http://meishi.qq.com/beijing/c/all\").thread(4).run();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/recover/DuplicateStorageRemover.java",
    "content": "package us.codecraft.webmagic.recover;\n\nimport com.google.common.base.Charsets;\nimport com.google.common.hash.BloomFilter;\nimport com.google.common.hash.Funnels;\nimport org.mapdb.DB;\nimport org.mapdb.DBMaker;\nimport org.mapdb.IndexTreeList;\nimport org.mapdb.Serializer;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.scheduler.component.DuplicateRemover;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author ：linweisen\n */\npublic class DuplicateStorageRemover implements DuplicateRemover {\n\n    private DB db;\n\n    private static String DATABASE_NAME = \"duplicate\";\n\n    private IndexTreeList<String> urlDuplicateQueue;\n\n    private BloomFilter<CharSequence> bloomFilter;\n\n    private AtomicInteger counter;\n\n    public DuplicateStorageRemover(String path) {\n\n        String duplicatStoragePath = path;\n\n        DB db = DBMaker.fileDB(duplicatStoragePath)\n                .fileMmapEnableIfSupported()\n                .fileMmapPreclearDisable()\n                .cleanerHackEnable()\n                .closeOnJvmShutdown()\n                .transactionEnable()\n                .concurrencyScale(128)\n                .make();\n        this.db = db;\n\n        this.urlDuplicateQueue = db.indexTreeList(DATABASE_NAME, Serializer.STRING).createOrOpen();\n\n        counter = new AtomicInteger(this.urlDuplicateQueue.size());\n        this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 200000, 1E-7);\n        for (String url : this.urlDuplicateQueue){\n            bloomFilter.put(url);\n        }\n\n    }\n\n    @Override\n    public boolean isDuplicate(Request request, Task task) {\n        String url = request.getUrl();\n        boolean isDuplicate = bloomFilter.mightContain(url);\n        if (!isDuplicate) {\n            bloomFilter.put(url);\n            urlDuplicateQueue.add(url);\n            this.db.commit();\n            counter.incrementAndGet();\n        }\n        return isDuplicate;\n    }\n\n    @Override\n    public void resetDuplicateCheck(Task task) {\n        this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 200000, 1E-7);\n        this.urlDuplicateQueue.clear();\n    }\n\n    @Override\n    public int getTotalRequestsCount(Task task) {\n        return counter.get();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/recover/MmapQueueScheduler.java",
    "content": "package us.codecraft.webmagic.recover;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.apache.commons.lang3.StringUtils;\nimport org.mapdb.DB;\nimport org.mapdb.DBMaker;\nimport org.mapdb.IndexTreeList;\nimport org.mapdb.Serializer;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.scheduler.DuplicateRemovedScheduler;\nimport us.codecraft.webmagic.scheduler.component.DuplicateRemover;\n\nimport java.io.IOException;\n\n/**\n * @author ：linweisen\n */\npublic class MmapQueueScheduler extends DuplicateRemovedScheduler {\n\n    private DB db;\n\n    private static String DATABASE_NAME = \"queue\";\n\n    private IndexTreeList<String> queue;\n\n    private static ObjectMapper mapper;\n\n    public MmapQueueScheduler(DuplicateRemover duplicateRemover, String path) {\n        super.setDuplicateRemover(duplicateRemover);\n\n        String queuePath = path;\n\n        DB db = DBMaker.fileDB(queuePath)\n                .fileMmapEnableIfSupported()\n                .fileMmapPreclearDisable()\n                .cleanerHackEnable()\n                .closeOnJvmShutdown()\n                .transactionEnable()\n                .concurrencyScale(128)\n                .make();\n        this.db = db;\n        this.mapper = new ObjectMapper();\n        this.queue = db.indexTreeList(MmapQueueScheduler.DATABASE_NAME, Serializer.STRING).createOrOpen();\n    }\n\n    @Override\n    public Request poll(Task task) {\n        if (this.queue.size() > 0){\n            String s = queue.remove(0);\n            return fromJson(s, Request.class);\n        }else{\n            return null;\n        }\n\n    }\n\n    @Override\n    public void pushWhenNoDuplicate(Request request, Task task) {\n        queue.add(toJson(request));\n        this.db.commit();\n    }\n\n    public String toJson(Object object) {\n        try {\n            return mapper.writeValueAsString(object);\n        } catch (IOException e) {\n            logger.warn(\"write to json string error:\" + object, e);\n            return null;\n        }\n    }\n\n    public <T> T fromJson(String jsonString, Class<T> clazz) {\n        if (StringUtils.isEmpty(jsonString)) {\n            return null;\n        }\n        try {\n            return mapper.readValue(jsonString, clazz);\n        } catch (IOException e) {\n            logger.warn(\"parse json string error:\" + jsonString, e);\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/recover/RecoverSample.java",
    "content": "package us.codecraft.webmagic.recover;\n\n\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.samples.SinaBlogProcessor;\nimport us.codecraft.webmagic.scheduler.component.DuplicateRemover;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class RecoverSample {\n\n    public static void main(String[] args) {\n        String storage = \"queue\";\n        String duplicate = \"duplicate\";\n        Spider spider = new Spider(new SinaBlogProcessor());\n        DuplicateRemover remover = new DuplicateStorageRemover(duplicate);\n        spider.setScheduler(new MmapQueueScheduler(remover, storage));\n        spider.addUrl(\"http://blog.sina.com.cn/s/articlelist_1487828712_0_1.html\")\n                .run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/AlexanderMcqueenGoodsProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.scheduler.PriorityScheduler;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class AlexanderMcqueenGoodsProcessor implements PageProcessor {\n\n    private Site site = Site.me().setRetryTimes(3).setSleepTime(0);\n\n\n    public static final String URL_LIST = \"http://www\\\\.alexandermcqueen\\\\.cn/.*\";\n\n    public static final String URL_POST = \"http://www\\\\.alexandermcqueen\\\\.cn/cn/\\\\w+/.*\\\\.html\";\n\n    @Override\n    public void process(Page page) {\n        if (page.getUrl().regex(URL_POST).match()) {\n            page.putField(\"goodsName\", page.getHtml().xpath(\"//div[@id='description']/h1/tidyText()\"));\n            if (page.getResultItems().get(\"goodsName\") == null) {\n                page.setSkip(true);\n            }\n            page.putField(\"currency\", page.getHtml().xpath(\"//div[@id='description']//div[@class='itemBoxPrice']/span//span[@class='currency']/tidyText()\"));\n            page.putField(\"goodsPrice\", page.getHtml().xpath(\"//div[@id='description']//div[@class='itemBoxPrice']/span//span[@class='priceValue']/tidyText()\"));\n            page.putField(\"description\", page.getHtml()\n                    .xpath(\"//div[@id='tabbedDescription']//div[@class='tabbedDescription']//ul[@id='tabs']//li[@id='tab_description']/div[@id='description_pane']/tidyText()\"));\n            page.putField(\"material\", page.getHtml()\n                    .xpath(\"//div[@id='tabbedDescription']\" +\n                            \"//div[@class='tabbedDescription']\" +\n                            \"//ul[@id='tabs']\" +\n                            \"//li[@id='tab_description']\" +\n                            \"//div[@class='productProperty']\" +\n                            \"//div[@class='productPropertyRow']/span[2]/tidyText()\"));\n            page.putField(\"goodsCode\", page.getHtml()\n                    .xpath(\"//div[@id='tabbedDescription']\" +\n                            \"//div[@class='tabbedDescription']\" +\n                            \"//ul[@id='tabs']\" +\n                            \"//li[@id='tab_description']\" +\n                            \"//div[@class='productProperty']\" +\n                            \"//div[@class='productPropertyRow']//span[@id='modelFabricColorContainer']/tidyText()\"));\n            page.putField(\"goodsSize\", page.getHtml()\n                    .xpath(\"//div[@id='sizesContainer']//div[@id='sizes']//ul[@class='SizeW']\"));\n            page.putField(\"goodsColors\", page.getHtml()\n                    .xpath(\"//div[@id='colors']/ul/html()\"));\n        } else {\n            page.addTargetRequests(page.getHtml().links().regex(URL_POST).all(), 1000);\n            page.addTargetRequests(page.getHtml().links().regex(URL_LIST).all(), 1);\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new AlexanderMcqueenGoodsProcessor()).setScheduler(new PriorityScheduler())\n                .addUrl(\"http://www.alexandermcqueen.cn/sitemap.asp?tskay=E2F1A848\").thread(5).run();\n    }\n}"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/AmanzonPageProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.selector.Html;\n\nimport java.util.List;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class AmanzonPageProcessor implements PageProcessor{\n    public void process(Page page) {\n\n        Html html = page.getHtml();\n        List<String> questionList =  html.xpath(\"//table[@class='tgCustomerCommunityCenterColumn']//div[@class='content']//table[@class='dataGrid']//tr\").all();\n\n        if(questionList != null && questionList.size() > 1)\n        {\n            //i=0是列名称，所以i从1开始\n            for( int i = 1 ; i < questionList.size(); i++)\n            {\n                System.out.println(questionList.get(i));\n                Html tempHtml =  Html.create(\"<table>\"+questionList.get(i)+\"</table>\");\n                String comment = tempHtml.xpath(\"//td[@class='title']//a/text()\").toString();\n                System.out.println(comment);\n                String answerNum =  tempHtml.xpath(\"//td[@class='num']/text()\").toString();\n                System.out.println(answerNum);\n                String createTime = tempHtml.xpath(\"//td[3]/text()\").toString();\n                System.out.println(createTime);\n\n\t\t\t\t/* Document doc = Jsoup.parse(questionList.get(i));\n\t\t\t\t Html hmt  = Html.create(questionList.get(i)) ;\n\t\t\t     String str = hmt.links().toString();\n\t\t\t\t  String   content =   doc.getElementsByTag(\"a\").text();\n\t\t\t\t  String ss = doc.text();*/\n\n            }\n        }\n\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me();\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new AmanzonPageProcessor()).test(\"http://www.amazon.de/forum/Fx27CUFD8S7LJ5D\");\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/AngularJSProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\n\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.selector.JsonPathSelector;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.5.0\n */\npublic class AngularJSProcessor implements PageProcessor {\n\n    private Site site = Site.me();\n\n    private static final String ARITICALE_URL = \"http://angularjs\\\\.cn/api/article/\\\\w+\";\n\n    private static final String LIST_URL = \"http://angularjs\\\\.cn/api/article/latest.*\";\n\n    @Override\n    public void process(Page page) {\n        if (page.getUrl().regex(LIST_URL).match()) {\n            List<String> ids = new JsonPathSelector(\"$.data[*]._id\").selectList(page.getRawText());\n            if (CollectionUtils.isNotEmpty(ids)) {\n                for (String id : ids) {\n                    page.addTargetRequest(\"http://angularjs.cn/api/article/\" + id);\n                }\n            }\n        } else {\n            page.putField(\"title\", new JsonPathSelector(\"$.data.title\").select(page.getRawText()));\n            page.putField(\"content\", new JsonPathSelector(\"$.data.content\").select(page.getRawText()));\n        }\n\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new AngularJSProcessor()).addUrl(\"http://angularjs.cn/api/article/latest?p=1&s=20\").run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/DiandianBlogProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class DiandianBlogProcessor implements PageProcessor {\n\n    private Site site;\n\n    @Override\n    public void process(Page page) {\n        //a()表示提取链接，links()表示提取所有链接\n        //getHtml()返回Html对象，支持链式调用\n        //r()表示用正则表达式提取一条内容，regex()表示提取多条内容\n        //toString()表示取单条结果，all()表示取多条\n        List<String> requests = page.getHtml().links().regex(\"(.*/post/.*)\").all();\n        //使用page.addTargetRequests()方法将待抓取的链接加入队列\n        page.addTargetRequests(requests);\n        //page.putField(key,value)将抽取的内容加入结果Map\n        //x()和xs()使用xpath进行抽取\n        page.putField(\"title\", page.getHtml().xpath(\"//title\").regex(\"(.*?)\\\\|\").toString());\n        //smartContent()使用readability技术直接抽取正文，对于规整的文本有比较好的抽取正确率\n        page.putField(\"content\", page.getHtml().smartContent());\n        page.putField(\"date\", page.getUrl().regex(\"post/(\\\\d+-\\\\d+-\\\\d+)/\"));\n        page.putField(\"id\", page.getUrl().regex(\"post/\\\\d+-\\\\d+-\\\\d+/(\\\\d+)\"));\n    }\n\n    @Override\n    public Site getSite() {\n        //site定义抽取配置，以及开始url等\n        if (site == null) {\n            site = Site.me().setDomain(\"progressdaily.diandian.com\").\n                    setUserAgent(\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31\");\n        }\n        return site;\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/DiaoyuwengProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.selector.PlainText;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-4-21\n * Time: 下午8:08\n */\npublic class DiaoyuwengProcessor implements PageProcessor {\n\n    private Site site;\n\n    @Override\n    public void process(Page page) {\n        List<String> requests = page.getHtml().links().regex(\"(http://www\\\\.diaoyuweng\\\\.com/home\\\\.php\\\\?mod=space&uid=88304&do=thread&view=me&type=thread&order=dateline&from=space&page=\\\\d+)\").all();\n        page.addTargetRequests(requests);\n        requests = page.getHtml().links().regex(\"(http://www\\\\.diaoyuweng\\\\.com/thread-\\\\d+-1-1.html)\").all();\n        page.addTargetRequests(requests);\n        if (page.getUrl().toString().contains(\"thread\")){\n            page.putField(\"title\", page.getHtml().xpath(\"//a[@id='thread_subject']\"));\n            page.putField(\"content\", page.getHtml().xpath(\"//div[@class='pcb']//tbody/tidyText()\"));\n            page.putField(\"date\",page.getHtml().regex(\"发表于 (\\\\d{4}-\\\\d+-\\\\d+ \\\\d+:\\\\d+:\\\\d+)\"));\n            page.putField(\"id\",new PlainText(\"1000\"+page.getUrl().regex(\"http://www\\\\.diaoyuweng\\\\.com/thread-(\\\\d+)-1-1.html\").toString()));\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        if (site==null){\n            site= Site.me().setDomain(\"www.diaoyuweng.com\").\n                    setUserAgent(\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31\").setCharset(\"GBK\").setSleepTime(500);\n        }\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new DiaoyuwengProcessor()).addUrl(\"http://www.diaoyuweng.com/home.php?mod=space&uid=88304&do=thread&view=me&type=thread&from=space\").run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/F58PageProcesser.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.scheduler.RedisScheduler;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-4-21\n * Time: 下午1:48\n */\npublic class F58PageProcesser implements PageProcessor {\n\n    @Override\n    public void process(Page page) {\n        List<String> strings = page.getHtml().links().regex(\".*/yewu/.*\").all();\n        page.addTargetRequests(strings);\n        page.putField(\"title\",page.getHtml().regex(\"<title>(.*)</title>\"));\n        page.putField(\"body\",page.getHtml().xpath(\"//dd\"));\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me().setDomain(\"sh.58.com\").setCycleRetryTimes(2);  //To change body of implemented methods use File | Settings | File Templates.\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new F58PageProcesser()).setScheduler(new RedisScheduler(\"localhost\")).addUrl(\"http://sh1.51a8.com/\").run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/GithubRepo.java",
    "content": "package us.codecraft.webmagic.samples;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class GithubRepo {\n\n    private String name;\n\n    private String author;\n\n    private String readme;\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public void setAuthor(String author) {\n        this.author = author;\n    }\n\n    public String getReadme() {\n        return readme;\n    }\n\n    public void setReadme(String readme) {\n        this.readme = readme;\n    }\n}"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/GithubRepoPageProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n * @author code4crafter@gmail.com <br>\n * @since 0.5.1\n */\npublic class GithubRepoPageProcessor implements PageProcessor {\n\n    private Site site = Site.me().setRetryTimes(3).setSleepTime(0);\n\n    @Override\n    public void process(Page page) {\n        page.addTargetRequests(page.getHtml().links().regex(\"(https://github\\\\.com/\\\\w+/\\\\w+)\").all());\n        page.addTargetRequests(page.getHtml().links().regex(\"(https://github\\\\.com/\\\\w+)\").all());\n        GithubRepo githubRepo = new GithubRepo();\n        githubRepo.setAuthor(page.getUrl().regex(\"https://github\\\\.com/(\\\\w+)/.*\").toString());\n        githubRepo.setName(page.getHtml().xpath(\"//h1[contains(@class, 'entry-title') and contains(@class, 'public')]/strong/a/text()\").toString());\n        githubRepo.setReadme(page.getHtml().xpath(\"//div[@id='readme']/tidyText()\").toString());\n        if (githubRepo.getName() == null) {\n            //skip this page\n            page.setSkip(true);\n        } else {\n            page.putField(\"repo\", githubRepo);\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new GithubRepoPageProcessor()).addUrl(\"https://github.com/code4craft\").thread(5).run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/HuxiuProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class HuxiuProcessor implements PageProcessor {\n    @Override\n    public void process(Page page) {\n        List<String> requests = page.getHtml().links().regex(\".*article.*\").all();\n        page.addTargetRequests(requests);\n        page.putField(\"title\",page.getHtml().xpath(\"//div[@class='clearfix neirong']//h1/text()\"));\n        page.putField(\"content\",page.getHtml().xpath(\"//div[@id='neirong_box']/tidyText()\"));\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me().setDomain(\"www.huxiu.com\");\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new HuxiuProcessor()).addUrl(\"http://www.huxiu.com/\").run();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/InfoQMiniBookProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport org.apache.commons.collections4.CollectionUtils;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class InfoQMiniBookProcessor implements PageProcessor {\n\n    private Site site;\n\n    @Override\n    public void process(Page page) {\n        page.addTargetRequests(page.getHtml().links().regex(\"http://www\\\\.infoq\\\\.com/cn/minibooks/.*\").all());\n        List<String> all = page.getHtml().links().regex(\".*\\\\.pdf\").all();\n        if (CollectionUtils.isNotEmpty(all)) {\n            page.putField(\"pdf\", all);\n        } else {\n            page.getResultItems().setSkip(true);\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        if (site == null) {\n            site = Site.me().setDomain(\"www.infoq.com\").addCookie(\"RegisteredUserCookie\", \"sDDDc8dIAgZSq67uJSXhtpQaHEi1XDOH\").\n                    setUserAgent(\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31\");\n        }\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new InfoQMiniBookProcessor())\n                .thread(5)\n                .addUrl(\"http://www.infoq.com/cn/minibooks\")\n                .run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/IteyeBlogProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class IteyeBlogProcessor implements PageProcessor {\n\n    private Site site;\n\n    @Override\n    public void process(Page page) {\n        page.addTargetRequests(page.getHtml().links().regex(\".*yanghaoli\\\\.iteye\\\\.com/blog/\\\\d+\").all());\n        page.putField(\"title\",page.getHtml().xpath(\"//title\").toString());\n        page.putField(\"content\",page.getHtml().smartContent().toString());\n    }\n\n    @Override\n    public Site getSite() {\n        if (site == null) {\n            site = Site.me().setDomain(\"yanghaoli.iteye.com\");\n        }\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new IteyeBlogProcessor()).thread(5).addUrl(\"http://yanghaoli.iteye.com/\").run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/KaichibaProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-5-20\n * Time: 下午5:31\n */\npublic class KaichibaProcessor implements PageProcessor {\n    @Override\n    public void process(Page page) {\n        //http://progressdaily.diandian.com/post/2013-01-24/40046867275\n        int i = Integer.valueOf(page.getUrl().regex(\"shop/(\\\\d+)\").toString()) + 1;\n        page.addTargetRequest(\"http://kaichiba.com/shop/\" + i);\n        page.putField(\"title\",page.getHtml().xpath(\"//Title\"));\n        page.putField(\"items\", page.getHtml().xpath(\"//li[@class=\\\"foodTitle\\\"]\").replace(\"^\\\\s+\", \"\").replace(\"\\\\s+$\", \"\").replace(\"<span>.*?</span>\", \"\"));\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me().setDomain(\"kaichiba.com\").setCharset(\"utf-8\").\n                setUserAgent(\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31\");\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new KaichibaProcessor()).addUrl(\"http://kaichiba.com/shop/41725781\").run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/MamacnPageProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.samples.pipeline.OneFilePipeline;\nimport us.codecraft.webmagic.scheduler.FileCacheQueueScheduler;\nimport us.codecraft.webmagic.selector.Selectable;\n\nimport java.io.FileNotFoundException;\nimport java.io.UnsupportedEncodingException;\nimport java.util.List;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class MamacnPageProcessor implements PageProcessor {\n\n    private Site site = Site.me().setDomain(\"www.mama.cn\").setSleepTime(100);\n\n    @Override\n    public void process(Page page) {\n        List<Selectable> nodes = page.getHtml().xpath(\"//ul[@id=ma-thumb-list]/li\").nodes();\n        StringBuilder accum = new StringBuilder();\n        for (Selectable node : nodes) {\n            accum.append(\"img:\").append(node.xpath(\"//a/@href\").get()).append(\"\\n\");\n            accum.append(\"title:\").append(node.xpath(\"//img/@alt\").get()).append(\"\\n\");\n        }\n        page.putField(\"\",accum.toString());\n        if (accum.length() == 0) {\n            page.setSkip(true);\n        }\n        page.addTargetRequests(page.getHtml().links().regex(\"http://www\\\\.mama\\\\.cn/photo/.*\\\\.html\").all());\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {\n        Spider.create(new MamacnPageProcessor())\n                .setScheduler(new FileCacheQueueScheduler(\"/data/webmagic/mamacn\"))\n                .addUrl(\"http://www.mama.cn/photo/t1-p1.html\")\n                .addPipeline(new OneFilePipeline(\"/data/webmagic/mamacn/data\"))\n                .thread(5)\n                .run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/MeicanProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-5-20\n * Time: 下午5:31\n */\npublic class MeicanProcessor implements PageProcessor {\n    @Override\n    public void process(Page page) {\n        //http://progressdaily.diandian.com/post/2013-01-24/40046867275\n        List<String> requests = page.getHtml().xpath(\"//a[@class=\\\"area_link flat_btn\\\"]/@href\").all();\n        if (requests.size() > 2) {\n            requests = requests.subList(0, 2);\n        }\n        page.addTargetRequests(requests);\n        page.addTargetRequests(page.getHtml().links().regex(\"(.*/restaurant/[^#]+)\").all());\n        page.putField(\"items\", page.getHtml().xpath(\"//ul[@class=\\\"dishes menu_dishes\\\"]/li/span[@class=\\\"name\\\"]/text()\"));\n        page.putField(\"prices\", page.getHtml().xpath(\"//ul[@class=\\\"dishes menu_dishes\\\"]/li/span[@class=\\\"price_outer\\\"]/span[@class=\\\"price\\\"]/text()\"));\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me().setDomain(\"meican.com\").setCharset(\"utf-8\").\n                setUserAgent(\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31\");\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new MeicanProcessor()).addUrl(\"http://www.meican.com/shanghai/districts\").run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/NjuBBSProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-4-21\n * Time: 下午8:08\n */\npublic class NjuBBSProcessor implements PageProcessor {\n    @Override\n    public void process(Page page) {\n        List<String> requests = page.getHtml().regex(\"<a[^<>]*href=(bbstcon\\\\?board=Pictures&file=[^>]*)\").all();\n        page.addTargetRequests(requests);\n        page.putField(\"title\",page.getHtml().xpath(\"//div[@id='content']//h2/a\"));\n        page.putField(\"content\",page.getHtml().smartContent());\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me().setDomain(\"bbs.nju.edu.cn\");\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new NjuBBSProcessor()).addUrl(\"http://bbs.nju.edu.cn/board?board=Pictures\").run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/PhantomJSPageProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.downloader.PhantomJSDownloader;\nimport us.codecraft.webmagic.pipeline.CollectorPipeline;\nimport us.codecraft.webmagic.pipeline.ResultItemsCollectorPipeline;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.List;\n\n/**\n * Created by dolphineor on 2014-11-21.\n * <p>\n * 以淘宝为例, 搜索冬装的相关结果\n */\npublic class PhantomJSPageProcessor implements PageProcessor {\n\n    private Site site = Site.me()\n            .setDomain(\"s.taobao.com\")\n            .setCharset(\"GBK\")\n            .addHeader(\"Referer\", \"http://www.taobao.com/\")\n            .setRetryTimes(3).setSleepTime(1000);\n\n    @Override\n    public void process(Page page) {\n        if (page.getRawText() != null)\n            page.putField(\"html\", page.getRawText());\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) throws Exception {\n        PhantomJSDownloader phantomDownloader = new PhantomJSDownloader();\n\n        CollectorPipeline<ResultItems> collectorPipeline = new ResultItemsCollectorPipeline();\n\n        Spider.create(new PhantomJSPageProcessor())\n                .addUrl(\"http://s.taobao.com/search?q=%B6%AC%D7%B0&sort=sale-desc\") //%B6%AC%D7%B0为冬装的GBK编码\n                .setDownloader(phantomDownloader)\n                .addPipeline(collectorPipeline)\n                .thread((Runtime.getRuntime().availableProcessors() - 1) << 1)\n                .run();\n\n        List<ResultItems> resultItemsList = collectorPipeline.getCollected();\n        System.out.println(resultItemsList.get(0).get(\"html\").toString());\n    }\n\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/QzoneBlogProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class QzoneBlogProcessor implements PageProcessor {\n    @Override\n    public void process(Page page) {\n        //http://progressdaily.diandian.com/post/2013-01-24/40046867275\n\n        //http://b1.cnc.qzone.qq.com/cgi-bin/blognew/get_abs?hostUin=233017404&uin=233017404&blogType=0&statYear=2013&source=0&statYear=2013&g_tk=291639571&g_tk=291639571&reqInfo=7&pos=0&num=15&source=0&rand=0.46480297949165106\n        // &cateName=&cateHex=&statYear=2013&reqInfo=7&pos=0&num=15&sortType=0&source=0&rand=0.46480297949165106&g_tk=291639571&verbose=1&ref=qzone\n        List<String> requests = page.getHtml().regex(\"<a[^<>]*href=[\\\"']{1}(http://17dujingdian\\\\.com/post/[^#]*?)[\\\"']{1}\").all();\n        page.addTargetRequests(requests);\n        page.putField(\"title\",page.getHtml().xpath(\"//div[@id='content']//h2/a\"));\n        page.putField(\"content\",page.getHtml().smartContent());\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me().setDomain(\"www.diandian.com\").\n                setUserAgent(\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31\");\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/SinaBlogProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class SinaBlogProcessor implements PageProcessor {\n\n    public static final String URL_LIST = \"http://blog\\\\.sina\\\\.com\\\\.cn/s/articlelist_1487828712_0_\\\\d+\\\\.html\";\n\n    public static final String URL_POST = \"http://blog\\\\.sina\\\\.com\\\\.cn/s/blog_\\\\w+\\\\.html\";\n\n    private Site site = Site\n            .me()\n            .setDomain(\"blog.sina.com.cn\")\n            .setSleepTime(3000)\n            .setUserAgent(\n                    \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31\");\n\n    @Override\n    public void process(Page page) {\n        //列表页\n        if (page.getUrl().regex(URL_LIST).match()) {\n            page.addTargetRequests(page.getHtml().xpath(\"//div[@class=\\\"articleList\\\"]\").links().regex(URL_POST).all());\n            page.addTargetRequests(page.getHtml().links().regex(URL_LIST).all());\n            //文章页\n        } else {\n            page.putField(\"title\", page.getHtml().xpath(\"//div[@class='articalTitle']/h2\"));\n            page.putField(\"content\", page.getHtml().xpath(\"//div[@id='articlebody']//div[@class='articalContent']\"));\n            page.putField(\"date\",\n                    page.getHtml().xpath(\"//div[@id='articlebody']//span[@class='time SG_txtc']\").regex(\"\\\\((.*)\\\\)\"));\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new SinaBlogProcessor()).addUrl(\"http://blog.sina.com.cn/s/articlelist_1487828712_0_1.html\")\n                .run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/TianyaPageProcesser.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com <br>\n */\npublic class TianyaPageProcesser implements PageProcessor {\n\n    @Override\n    public void process(Page page) {\n        List<String> strings = page.getHtml().regex(\"<a[^<>]*href=[\\\"']{1}(/post-free.*?\\\\.shtml)[\\\"']{1}\").all();\n        page.addTargetRequests(strings);\n        page.putField(\"title\", page.getHtml().xpath(\"//div[@id='post_head']//span[@class='s_title']//b\"));\n        page.putField(\"body\",page.getHtml().smartContent());\n    }\n\n    @Override\n    public Site getSite() {\n        return Site.me().setDomain(\"http://bbs.tianya.cn/\");  //To change body of implemented methods use File | Settings | File Templates.\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/ZhihuPageProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.pipeline.FilePipeline;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.selector.Html;\n\nimport java.util.List;\n\n/**\n * @author 410775541@qq.com <br>\n * @since 0.5.1\n */\npublic class ZhihuPageProcessor implements PageProcessor {\n\n    private Site site = Site.me().setCycleRetryTimes(5).setRetryTimes(5).setSleepTime(500).setTimeOut(3 * 60 * 1000)\n            .setUserAgent(\"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0\")\n            .addHeader(\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\")\n            .addHeader(\"Accept-Language\", \"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3\")\n            .setCharset(\"UTF-8\");\n\n    private static final int voteNum = 1000;\n\n\n    @Override\n    public void process(Page page) {\n        List<String> relativeUrl = page.getHtml().xpath(\"//li[@class='item clearfix']/div/a/@href\").all();\n        page.addTargetRequests(relativeUrl);\n        relativeUrl = page.getHtml().xpath(\"//div[@id='zh-question-related-questions']//a[@class='question_link']/@href\").all();\n        page.addTargetRequests(relativeUrl);\n        List<String> answers =  page.getHtml().xpath(\"//div[@id='zh-question-answer-wrap']/div\").all();\n        boolean exist = false;\n        for(String answer:answers){\n            String vote = new Html(answer).xpath(\"//div[@class='zm-votebar']//span[@class='count']/text()\").toString();\n            if(Integer.valueOf(vote) >= voteNum){\n                page.putField(\"vote\",vote);\n                page.putField(\"content\",new Html(answer).xpath(\"//div[@class='zm-editable-content']\"));\n                page.putField(\"userid\", new Html(answer).xpath(\"//a[@class='author-link']/@href\"));\n                exist = true;\n            }\n        }\n        if(!exist){\n            page.setSkip(true);\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new ZhihuPageProcessor()).\n                addUrl(\"http://www.zhihu.com/search?type=question&q=java\").\n                addPipeline(new FilePipeline(\"D:\\\\webmagic\\\\\")).\n                thread(5).\n                run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/formatter/StringTemplateFormatter.java",
    "content": "package us.codecraft.webmagic.samples.formatter;\n\nimport us.codecraft.webmagic.model.formatter.ObjectFormatter;\n\n/**\n * @author yihua.huang@dianping.com\n */\npublic class StringTemplateFormatter implements ObjectFormatter<String> {\n\n    private String template;\n\n    @Override\n    public String format(String raw) throws Exception {\n        return String.format(template, raw);\n    }\n\n    @Override\n    public Class<String> clazz() {\n        return String.class;\n    }\n\n    @Override\n    public void initParam(String[] extra) {\n        template = extra[0];\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/pipeline/OneFilePipeline.java",
    "content": "package us.codecraft.webmagic.samples.pipeline;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.pipeline.Pipeline;\nimport us.codecraft.webmagic.utils.FilePersistentBase;\n\nimport java.io.*;\nimport java.util.Map;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class OneFilePipeline extends FilePersistentBase implements Pipeline {\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    private PrintWriter printWriter;\n\n    public OneFilePipeline() throws FileNotFoundException, UnsupportedEncodingException {\n        this(\"/data/webmagic/\");\n    }\n\n    public OneFilePipeline(String path) throws FileNotFoundException, UnsupportedEncodingException {\n        setPath(path);\n        printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(getFile(path)), \"UTF-8\"));\n    }\n\n    @Override\n    public synchronized void process(ResultItems resultItems, Task task) {\n        printWriter.println(\"url:\\t\" + resultItems.getRequest().getUrl());\n        for (Map.Entry<String, Object> entry : resultItems.getAll().entrySet()) {\n            if (entry.getValue() instanceof Iterable) {\n                Iterable value = (Iterable) entry.getValue();\n                printWriter.println(entry.getKey() + \":\");\n                for (Object o : value) {\n                    printWriter.println(o);\n                }\n            } else {\n                printWriter.println(entry.getKey() + \":\\t\" + entry.getValue());\n            }\n        }\n        printWriter.flush();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/pipeline/ReplacePipeline.java",
    "content": "package us.codecraft.webmagic.samples.pipeline;\n\n/**\n * @author code4crafer@gmail.com\n */\npublic class ReplacePipeline {\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/scheduler/DelayQueueScheduler.java",
    "content": "package us.codecraft.webmagic.samples.scheduler;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.scheduler.PriorityScheduler;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.concurrent.DelayQueue;\nimport java.util.concurrent.Delayed;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class DelayQueueScheduler extends PriorityScheduler {\n\n    private DelayQueue<RequestWrapper> queue = new DelayQueue<RequestWrapper>();\n\n    private Set<String> urls = new HashSet<String>();\n\n    private long time;\n\n    private TimeUnit timeUnit;\n\n    private class RequestWrapper implements Delayed {\n\n        private long startTime = System.currentTimeMillis();\n\n        private Request request;\n\n        private RequestWrapper(Request request) {\n            this.request = request;\n        }\n\n        private long getStartTime() {\n            return startTime;\n        }\n\n        private Request getRequest() {\n            return request;\n        }\n\n        @Override\n        public long getDelay(TimeUnit unit) {\n            long convert = unit.convert(TimeUnit.MILLISECONDS.convert(time, timeUnit) - System.currentTimeMillis() + startTime, TimeUnit.MILLISECONDS);\n            return convert;\n        }\n\n        @Override\n        public int compareTo(Delayed o) {\n            return new Long(getDelay(TimeUnit.MILLISECONDS)).compareTo(o.getDelay(TimeUnit.MILLISECONDS));\n        }\n    }\n\n    public DelayQueueScheduler(long time, TimeUnit timeUnit) {\n        this.time = time;\n        this.timeUnit = timeUnit;\n    }\n\n    @Override\n    public synchronized void push(Request request, Task task) {\n        if (urls.add(request.getUrl())) {\n            queue.add(new RequestWrapper(request));\n        }\n\n    }\n\n    @Override\n    public synchronized Request poll(Task task) {\n        RequestWrapper take = null;\n        while (take == null) {\n            try {\n                take = queue.take();\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n        queue.add(new RequestWrapper(take.getRequest()));\n        return take.getRequest();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/scheduler/LevelLimitScheduler.java",
    "content": "package us.codecraft.webmagic.samples.scheduler;\n\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.scheduler.PriorityScheduler;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class LevelLimitScheduler extends PriorityScheduler {\n\n    private int levelLimit = 3;\n\n    public LevelLimitScheduler(int levelLimit) {\n        this.levelLimit = levelLimit;\n    }\n\n    @Override\n    public synchronized void push(Request request, Task task) {\n        if (((Integer) request.getExtra(\"_level\")) <= levelLimit) {\n            super.push(request, task);\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/java/us/codecraft/webmagic/samples/scheduler/ZipCodePageProcessor.java",
    "content": "package us.codecraft.webmagic.samples.scheduler;\n\nimport org.apache.commons.lang3.StringUtils;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.scheduler.PriorityScheduler;\n\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static us.codecraft.webmagic.selector.Selectors.xpath;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class ZipCodePageProcessor implements PageProcessor {\n\n    private Site site = Site.me().setCharset(\"gb2312\")\n            .setSleepTime(100);\n\n    @Override\n    public void process(Page page) {\n        if (page.getUrl().toString().equals(\"http://www.ip138.com/post/\")) {\n            processCountry(page);\n        } else if (page.getUrl().regex(\"http://www\\\\.ip138\\\\.com/\\\\d{6}[/]?$\").toString() != null) {\n            processDistrict(page);\n        } else {\n            processProvince(page);\n        }\n\n    }\n\n    private void processCountry(Page page) {\n        List<String> provinces = page.getHtml().xpath(\"//*[@id=\\\"newAlexa\\\"]/table/tbody/tr/td\").all();\n        for (String province : provinces) {\n            String link = xpath(\"//@href\").select(province);\n            String title = xpath(\"/text()\").select(province);\n            Request request = new Request(link).setPriority(0).putExtra(\"province\", title);\n            page.addTargetRequest(request);\n        }\n    }\n\n    private void processProvince(Page page) {\n        //这里仅靠xpath没法精准定位，所以使用正则作为筛选，不符合正则的会被过滤掉\n        List<String> districts = page.getHtml().xpath(\"//body/table/tbody/tr[@bgcolor=\\\"#ffffff\\\"]\").all();\n        Pattern pattern = Pattern.compile(\"<td>([^<>]+)</td>.*?href=\\\"(.*?)\\\"\",Pattern.DOTALL);\n        for (String district : districts) {\n            Matcher matcher = pattern.matcher(district);\n            while (matcher.find()) {\n                String title = matcher.group(1);\n                String link = matcher.group(2);\n                Request request = new Request(link).setPriority(1).putExtra(\"province\", page.getRequest().getExtra(\"province\")).putExtra(\"district\", title);\n                page.addTargetRequest(request);\n            }\n        }\n    }\n\n    private void processDistrict(Page page) {\n        String province = page.getRequest().getExtra(\"province\").toString();\n        String district = page.getRequest().getExtra(\"district\").toString();\n        String zipCode = page.getHtml().regex(\"<h2>邮编：(\\\\d+)</h2>\").toString();\n        page.putField(\"result\", StringUtils.join(new String[]{province, district,\n                zipCode}, \"\\t\"));\n        List<String> links = page.getHtml().links().regex(\"http://www\\\\.ip138\\\\.com/\\\\d{6}[/]?$\").all();\n        for (String link : links) {\n            page.addTargetRequest(new Request(link).setPriority(2).putExtra(\"province\", province).putExtra(\"district\", district));\n        }\n\n    }\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider spider = Spider.create(new ZipCodePageProcessor()).scheduler(new PriorityScheduler()).addUrl(\"http://www.ip138.com/post/\");\n\n        spider.run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/main/resources/crawl.js",
    "content": "var system = require('system');\nvar url = system.args[1];\n\nvar page = require('webpage').create();\npage.settings.loadImages = false;\npage.settings.resourceTimeout = 5000;\n\npage.open(url, function (status) {\n    if (status != 'success') {\n        console.log(\"HTTP request failed!\");\n    } else {\n        console.log(page.content);\n    }\n\n    page.close();\n    phantom.exit();\n});"
  },
  {
    "path": "webmagic-samples/src/main/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration>\n    <Appenders>\n        <Console name=\"stdout\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"%d{yy-MM-dd HH:mm:ss,SSS} %-5p %c(%F:%L) ## %m%n\" />\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Logger name=\"org.springframework\" level=\"warn\" additivity=\"false\">\n            <AppenderRef ref=\"stdout\" />\n        </Logger>\n        <Logger name=\"net.sf.ehcache\" level=\"warn\" additivity=\"false\">\n            <AppenderRef ref=\"stdout\" />\n        </Logger>\n        <Root level=\"info\">\n            <AppenderRef ref=\"stdout\" />\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "webmagic-samples/src/test/java/us/codecraft/webmagic/SpiderTest.java",
    "content": "package us.codecraft.webmagic;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.pipeline.FilePipeline;\nimport us.codecraft.webmagic.processor.SimplePageProcessor;\nimport us.codecraft.webmagic.samples.HuxiuProcessor;\nimport us.codecraft.webmagic.scheduler.FileCacheQueueScheduler;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-4-20\n * Time: 下午7:46\n */\npublic class SpiderTest {\n\n\n    @Ignore\n    @Test\n    public void testSpider() throws InterruptedException {\n        Spider me = Spider.create(new HuxiuProcessor()).addPipeline(new FilePipeline());\n        me.run();\n    }\n\n    @Ignore\n    @Test\n    public void testGlobalSpider(){\n//        PageProcessor pageProcessor = new MeicanProcessor();\n//        Spider.me().pipeline(new FilePipeline()).scheduler(new FileCacheQueueScheduler(pageProcessor.getSite(),\"/data/temp/webmagic/cache/\")).\n//                processor(pageProcessor).run();\n        SimplePageProcessor pageProcessor2 = new SimplePageProcessor( \"http://www.diaoyuweng.com/thread-*-1-1.html\");\n        System.out.println(pageProcessor2.getSite().getCharset());\n        pageProcessor2.getSite().setSleepTime(500);\n        Spider.create(pageProcessor2).addUrl(\"http://www.diaoyuweng.com/home.php?mod=space&uid=88304&do=thread&view=me&type=thread&from=space\").addPipeline(new FilePipeline()).scheduler(new FileCacheQueueScheduler(\"/data/temp/webmagic/cache/\")).\n                run();\n\n\n    }\n\n    @Ignore\n    @Test\n    public void test(){\n        System.out.println(System.getProperty(\"java.io.tmpdir\"));\n    }\n\n\n    @Ignore\n    @Test\n    public void languageSchema() {\n\n\n        /**\n         *\n         * _hrefs = regex(\"<a[^<>]*href=[\\\"']{1}(/yewu/.*?)[\\\"']{1}\")\n         * title = r(\"\"<title>(.*)</title>\"\")\n         * body = x(\"//dd[@class='w133']\")\n         *\n         * site.domain = \"sh.58.com\"\n         * site.ua=\"\"\n         * site.cookie=\"aa:bb\"\n         *\n         */\n\n        /**\n         *\n         *\n         * if (page == r('') && refer(1) == 1) {\n         *\n         *      type = _refer(1)\n         *      content = _text.t().c()\n         *      title = x(\"asd@asd\").r(\"\",1)\n         *      body[r(_currentUrl).g(1)] = body[r(_currentUrl).g(1)] + (x(\"\").r(\"\",1,2).c())\n         *\n         *      body=body[r(_currentUrl).g(1)]\n         *      tags[%] = (tags[%] + xpath('')) . r('')\n         *\n         *      _targetUrls.add('' + x('').r(''))\n         *      _sourceUrls.add()\n         *      _header.put(\"\",\"\");\n         *      _cookie.add(\"asdsadasdsa\");\n         *\n         *\n         * }\n         *\n         * _cookie.add(_cookie[''])\n         *\n         * if (page == r('') && refer(1) == 1)\n         *  (\n         *      _targetUrl = '' + x('') & r('')\n         *      _sourceUrl = ''\n         *  )\n         *\n         */\n\n        /**\n         * <condition></>\n         * <selector>\n         *     <fields>\n         *\n         *     <type>\n         *         <selector></selector>\n         *         <selector></selector>\n         *     </type>\n         *         </>\n         *     </>\n         */\n\n        /**\n         *\n         * if (model.url('') && model.refer(1) == 1)\n         *  (\n         *\n         *      model.set(type, model.refer(1))\n         *      content = t(_html) > c()\n         *      title = x(_html, 'asd@asd') > r('',1)\n         *      body[r(_currentUrl).g(1)] = body[r(_currentUrl).g(1)] + (x('') > r('',1,2) > c()) | x('')\n         *      tags[%] = tags + xpath('') > r('')\n         *      model.setTargetUrl();\n         *\n         *      _targetUrl = '' + x('') & r('')\n         *      _sourceUrl = ''\n         * )\n         *\n         * _cookie.add(_cookie[''])\n         *\n         * if (page == r('') && refer(1) == 1)\n         *  (\n         *      _targetUrl = '' + x('') & r('')\n         *      _sourceUrl = ''\n         *  )\n         *\n         */\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/test/java/us/codecraft/webmagic/model/ProcessorBenchmark.java",
    "content": "package us.codecraft.webmagic.model;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.model.samples.OschinaBlog;\nimport us.codecraft.webmagic.selector.Html;\nimport us.codecraft.webmagic.selector.PlainText;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class ProcessorBenchmark {\n\n    @Ignore\n    @Test\n    public void test() {\n        ModelPageProcessor modelPageProcessor = ModelPageProcessor.create(Site.me(), OschinaBlog.class);\n        Page page = new Page();\n        page.setRequest(new Request(\"http://my.oschina.net/flashsword/blog\"));\n        page.setUrl(new PlainText(\"http://my.oschina.net/flashsword/blog\"));\n        page.setHtml(new Html(html));\n        long time = System.currentTimeMillis();\n        for (int i = 0; i < 1000; i++) {\n            modelPageProcessor.process(page);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 1000; i++) {\n            modelPageProcessor.process(page);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n    }\n\n    private String html = \"\\n\" +\n            \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\\n\" +\n            \"<html lang='zh-CN' xml:lang='zh-CN' xmlns='http://www.w3.org/1999/xhtml'>\\n\" +\n            \"<head>\\n\" +\n            \"  <meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"/>\\n\" +\n            \"  <meta http-equiv=\\\"Content-Language\\\" content=\\\"zh-CN\\\"/>\\n\" +\n            \"  <meta name=\\\"robots\\\" content=\\\"index, follow\\\" />\\n\" +\n            \"  <link rel=\\\"shortcut icon\\\" type=\\\"image/x-icon\\\" href=\\\"/img/favicon.ico\\\" />\\n\" +\n            \"  <title>Jsoup代码解读之八-防御XSS攻击 -  黄亿华的个人页面 - 开源中国社区</title>\\n\" +\n            \"    <meta name=\\\"Keywords\\\" content=\\\"Jsoup,XSS,OO\\\"/>\\n\" +\n            \"      <meta name=\\\"Description\\\" content=\\\"Jsoup代码解读之八-防御XSS攻击：![hacker][1] ## 防御XSS攻击的一般原理 cleaner是Jsoup的重要功能之一，我们常用它来进行富文本输入中的...\\\"/>\\n\" +\n            \"    <link rel=\\\"stylesheet/less\\\" href=\\\"http://my.oschina.net/flashsword/styles.less?ver=20130608&date=20130524070359\\\" type=\\\"text/css\\\" media=\\\"screen\\\" />\\n\" +\n            \"  <link rel=\\\"stylesheet\\\" href=\\\"/js/2012/poshytip/tip-yellowsimple/tip-yellowsimple.css\\\" type=\\\"text/css\\\" />\\n\" +\n            \"  <link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" href=\\\"/js/2011/fancybox/jquery.fancybox-1.3.4.css\\\" media=\\\"screen\\\" />\\n\" +\n            \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2012/jquery-1.7.1.min.js\\\"></script>\\n\" +\n            \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2012/jquery.form.js\\\"></script>\\n\" +\n            \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2011/fancybox/jquery.fancybox-1.3.4.pack.js\\\"></script>\\n\" +\n            \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2012/poshytip/jquery.poshytip.min.js\\\"></script>\\n\" +\n            \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2011/oschina.js?ver=20121007\\\"></script>\\n\" +\n            \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2012/less-1.3.0.min.js\\\"></script>\\n\" +\n            \"  <script type=\\\"text/javascript\\\" src=\\\"/js/scrolltopcontrol.js\\\"></script>\\n\" +\n            \"  <script type='text/javascript' src='/js/jquery/jquery.atwho.js'></script>\\n\" +\n            \"  <link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" href=\\\"/js/jquery/jquery.atwho.css\\\" />\\n\" +\n            \"  <link rel=\\\"alternate\\\" type=\\\"application/rss+xml\\\" title=\\\"黄亿华最新博客\\\" href=\\\"http://my.oschina.net/flashsword/rss\\\" />\\n\" +\n            \"  <link rel=\\\"EditURI\\\" type=\\\"application/rsd+xml\\\" title=\\\"RSD\\\" href=\\\"http://my.oschina.net/action/xmlrpc/rsd?space=190591\\\" />\\n\" +\n            \"  <link rel=\\\"wlwmanifest\\\" type=\\\"application/wlwmanifest+xml\\\" href=\\\"http://my.oschina.net/action/xmlrpc/wlwmanifest?space=190591\\\" /> \\n\" +\n            \"  <style type=\\\"text/css\\\">\\n\" +\n            \"    body,table,input,textarea,select {font-family:Verdana,sans-serif,宋体;}\\t\\n\" +\n            \"  </style>\\n\" +\n            \"  <script type=\\\"text/javascript\\\">\\n\" +\n            \"  \\tscrolltotop.offset(100,165);\\n\" +\n            \"\\tscrolltotop.init();\\n\" +\n            \"  </script>\\n\" +\n            \"</head>\\n\" +\n            \"<body>\\n\" +\n            \"<div id=\\\"OSC_Screen\\\">\\n\" +\n            \"\\t<div id='OSC_Banner'>\\n\" +\n            \"\\t\\t<div id=\\\"OSC_Logo\\\">\\n\" +\n            \"        \\t<a href=\\\"http://www.oschina.net/\\\" title=\\\"开源中国社区首页\\\">开源中国社区</a>\\n\" +\n            \"        </div>\\n\" +\n            \"        <div id='OSC_Slogon'>开源项目发现、使用和交流平台</div>\\n\" +\n            \"\\t\\t        <div id=\\\"OSC_Channels\\\">\\n\" +\n            \"        \\t<ul>\\n\" +\n            \"        \\t<li><a href=\\\"http://www.oschina.net/project\\\" class='software'>项目</a></li>\\n\" +\n            \"        \\t<li><a href=\\\"http://www.oschina.net/question\\\" class='question'>讨论</a></li>\\n\" +\n            \"        \\t<li><a href=\\\"http://www.oschina.net/code/list\\\" class='code'>代码</a></li>\\n\" +\n            \"        \\t<li><a href=\\\"http://www.oschina.net/news\\\" class='news'>资讯</a></li>\\n\" +\n            \"        \\t<li><a href=\\\"http://www.oschina.net/translate\\\" class='translate'>翻译</a></li>\\n\" +\n            \"        \\t<li><a href=\\\"http://www.oschina.net/blog\\\" class='blog'>博客</a></li>\\n\" +\n            \"        \\t<li><a href=\\\"http://www.oschina.net/android\\\" class='android'>Android</a></li>\\n\" +\n            \"        \\t<li><a href=\\\"http://www.oschina.net/job\\\" class='job'>招聘</a></li>\\n\" +\n            \"        \\t</ul>\\n\" +\n            \"        </div>\\n\" +\n            \"        <div class='clear'></div>\\n\" +\n            \"\\t</div>\\n\" +\n            \"\\t<div id=\\\"OSC_Topbar\\\">\\n\" +\n            \"\\t\\t<div id=\\\"VisitorInfo\\\">\\n\" +\n            \"\\t\\t当前访客身份：\\n\" +\n            \"\\t\\t\\t\\t黄亿华 [ <a href=\\\"/action/user/logout?session=6db40e6e2d1061998068&goto_page=http%3A%2F%2Fmy.oschina.net%2Fflashsword\\\">退出</a> ]\\n\" +\n            \"\\t\\t\\t\\t<span id=\\\"OSC_Notification\\\">\\t\\t\\t\\n\" +\n            \"\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t<a href=\\\"http://my.oschina.net/flashsword/admin/inbox\\\" class=\\\"msgbox\\\" title=\\\"进入我的留言箱\\\">你有<em>0</em>新留言</a>\\t\\t\\t\\n\" +\n            \"\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t</span>\\n\" +\n            \"\\t\\t</div>\\n\" +\n            \"\\t\\t<div id=\\\"SearchBar\\\">\\n\" +\n            \"    \\t\\t<form action=\\\"http://www.oschina.net/search\\\">\\n\" +\n            \"\\t\\t\\t\\t<input type='hidden' name='user' value='190591'/>\\n\" +\n            \"\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t<span class=\\\"ipt f_l\\\">\\n\" +\n            \"    \\t\\t\\t<input type='text' id='txt_q' name='q' class='SERACH' value='在 26755 款开源软件中搜索' onblur=\\\"(this.value=='')?this.value='在 26755 款开源软件中搜索':this.value\\\" onfocus=\\\"if(this.value=='在 26755 款开源软件中搜索'){this.value='';};this.select();\\\"/>\\n\" +\n            \"\\t\\t\\t\\t</span>\\n\" +\n            \"\\t\\t\\t\\t                <div class=\\\"search-by selectbox\\\">\\n\" +\n            \"    \\t\\t\\t\\t<span class=\\\"hide\\\">\\n\" +\n            \"    \\t\\t\\t\\t<select name='scope'>\\t\\t\\t\\t\\t\\n\" +\n            \"                        <option value='project' selected>软件</option>\\n\" +\n            \"                        <option value='code'>代码</option>\\n\" +\n            \"                        <option value='bbs'>讨论区</option>\\n\" +\n            \"                        <option value='news'>新闻</option>\\n\" +\n            \"                        <option value='blog'>博客</option>\\n\" +\n            \"    \\t\\t\\t\\t</select>\\n\" +\n            \"    \\t\\t\\t\\t</span>\\n\" +\n            \"                  <div class=\\\"search_on\\\" id=\\\"search-item\\\"><span class=\\\"text\\\">软件</span></div>\\n\" +\n            \"                  <ul class=\\\"search_list\\\">\\n\" +\n            \"                     <li class=\\\"search-item\\\"><a href=\\\"#1\\\">软件</a></li>\\n\" +\n            \"                     <li><a href=\\\"#2\\\">代码</a></li>\\n\" +\n            \"                     <li><a href=\\\"#3\\\">讨论区</a></li>\\n\" +\n            \"                     <li><a href=\\\"#4\\\">新闻</a></li>\\n\" +\n            \"                     <li><a href=\\\"#5\\\">博客</a></li>\\n\" +\n            \"                  </ul>\\n\" +\n            \"                </div>\\n\" +\n            \"\\t\\t\\t\\t<input type='submit' value='搜索' class='bnt f_r'/>\\t\\t\\t\\n\" +\n            \"    \\t\\t</form>\\n\" +\n            \"\\t\\t</div>\\n\" +\n            \"\\t\\t<div class='clear'></div>\\n\" +\n            \"\\t</div>\\n\" +\n            \"\\t<div id=\\\"OSC_Content\\\">\\t\\n\" +\n            \"\\n\" +\n            \"<div id='SpaceLeft'>\\n\" +\n            \"<div class='Owner'>\\n\" +\n            \"\\t\\t<a href='http://my.oschina.net/flashsword/admin/user-settings?tab=3' title='切换空间风格' class='ThemeSetting'>切换风格</a>    <a href=\\\"http://my.oschina.net/flashsword\\\" class='Img'><img src=\\\"http://static.oschina.net/uploads/user/95/190591_100.jpg?t=1347254905000\\\" align=\\\"absmiddle\\\" alt=\\\"黄亿华\\\" title=\\\"黄亿华\\\" class=\\\"LargePortrait\\\"/></a>\\n\" +\n            \"    <span class='U'>\\n\" +\n            \"        <a href=\\\"http://my.oschina.net/flashsword\\\" class='Name' title='男'>黄亿华</a>\\n\" +\n            \"\\t\\t<span class='opts'>\\n\" +\n            \"\\t\\t\\t<img src=\\\"/img/2012/men.png\\\" align='absmiddle' title='男'/>\\n\" +\n            \"        \\t\\t\\t<a href=\\\"http://my.oschina.net/flashsword/admin/profile\\\">修改资料</a>\\n\" +\n            \"\\t\\t\\t<a href=\\\"http://my.oschina.net/flashsword/admin/portrait\\\">更换头像</a>\\n\" +\n            \"        \\t\\t</span>\\n\" +\n            \"    </span>\\n\" +\n            \"    <div class='clear'></div>\\n\" +\n            \"    <div class='stat'>\\n\" +\n            \"    \\t<a href=\\\"http://my.oschina.net/flashsword/fellow\\\">关注(43)</a>\\n\" +\n            \"    \\t<a href=\\\"http://my.oschina.net/flashsword/fans\\\">粉丝(98)</a>\\n\" +\n            \"    \\t<a href=\\\"http://www.oschina.net/question/3307_20931\\\" title=\\\"查看OSCHINA积分规则\\\">积分(173)</a>\\n\" +\n            \"    </div>\\n\" +\n            \"</div><style>\\n\" +\n            \"#MyResume textarea {width:170px;height:60px;font-size:9pt;}\\n\" +\n            \"</style>\\n\" +\n            \"<div class='Resume' id='MyResume'>\\n\" +\n            \"码农一枚<br>实用主义者<br>抵制重复造轮子，却造了不少轮子<br>http://codecraft.us</div>\\n\" +\n            \"<script type=\\\"text/javascript\\\" src=\\\"/js/2012/jquery.editinplace.js\\\"></script>\\n\" +\n            \"<script type=\\\"text/javascript\\\">\\n\" +\n            \"$(\\\"#MyResume\\\").editInPlace({\\n\" +\n            \"    url: \\\"/action/profile/update_user_signature?user_code=tzm9Wg2YoU8SkJaTIjHQkahStiXQNyymUGXFOQgN\\\",\\n\" +\n            \"\\tbg_over: \\\"none\\\",\\n\" +\n            \"\\tbg_out: \\\"none\\\",\\n\" +\n            \"    field_type: \\\"textarea\\\",\\n\" +\n            \"\\tvalue_required: \\\"true\\\",\\n\" +\n            \"\\terror: function(){\\n\" +\n            \"\\t\\talert(\\\"修改个人简介失败\\\");\\n\" +\n            \"\\t}\\n\" +\n            \"});\\n\" +\n            \"</script>\\n\" +\n            \"\\n\" +\n            \"<div class='Opts clearfix'>\\n\" +\n            \"\\t<a href=\\\"http://my.oschina.net/flashsword/admin/new-blog\\\" class='a1 blog'><i>.</i><span>发表博文</span></a>\\n\" +\n            \"\\t<a href=\\\"http://my.oschina.net/flashsword/admin\\\" class='a2 admin'><i>.</i><span>空间管理</span></a>\\n\" +\n            \"</div><div class=\\\"Mod\\\" id=\\\"BlogCatalogs\\\">\\n\" +\n            \"  <strong><a href=\\\"http://my.oschina.net/flashsword/admin/blog-catalogs\\\" class=\\\"more\\\">管理&raquo;</a> 博客分类</strong>\\n\" +\n            \"  <ul>\\n\" +\n            \"\\t\\t\\t<li class='draft'><a href=\\\"http://my.oschina.net/flashsword/admin/drafts\\\">草稿箱</a><span>(4)</span></li>\\n\" +\n            \"\\t    \\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=371362\\\">webmagic</a><span>(16)</span></li>\\n\" +\n            \"\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=380473\\\">分布式消息系统</a><span>(5)</span></li>\\n\" +\n            \"\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=285504\\\">探耽求究</a><span>(5)</span></li>\\n\" +\n            \"\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=368513\\\">BlackHoleJ</a><span>(21)</span></li>\\n\" +\n            \"\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=368514\\\">Intellij</a><span>(4)</span></li>\\n\" +\n            \"\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=112331\\\">工作日志</a><span>(7)</span></li>\\n\" +\n            \"\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=112332\\\">日常记录</a><span>(4)</span></li>\\n\" +\n            \"\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=261044\\\">codecraft</a><span>(1)</span></li>\\n\" +\n            \"\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog?catalog=279271\\\">开发日记</a><span>(3)</span></li>\\n\" +\n            \"\\t  </ul>\\n\" +\n            \"</div><div class=\\\"Mod\\\" id=\\\"HotBlogs\\\">\\n\" +\n            \"  <strong>阅读排行</strong>\\n\" +\n            \"  <ol>\\n\" +\n            \"\\t\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog/145796\\\">1. webmagic的设计机制及原理-如何开发一个Java爬虫</a></li>\\n\" +\n            \"\\t\\t\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog/143028\\\">2. monkeysocks开发日志--TCP协议分析及架构规划</a></li>\\n\" +\n            \"\\t\\t\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog/156638\\\">3. 【整理】国内一些大公司的开源项目</a></li>\\n\" +\n            \"\\t\\t\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog/110276\\\">4. BlackHole开发日志--防止DNS污染</a></li>\\n\" +\n            \"\\t\\t\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog/158200\\\">5. Jsoup代码解读之八-防御XSS攻击</a></li>\\n\" +\n            \"\\t\\t\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog/123505\\\">6. IntelliJ IDEA使用心得</a></li>\\n\" +\n            \"\\t\\t\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog/80037\\\">7. 关于HTTP keep-alive的实验</a></li>\\n\" +\n            \"\\t\\t\\t\\t<li><a href=\\\"http://my.oschina.net/flashsword/blog/152263\\\">8. 分布式消息系统研究报告之Kafka</a></li>\\n\" +\n            \"\\t\\t  </ol>\\n\" +\n            \"</div>\\n\" +\n            \"<div class=\\\"Mod\\\" id=\\\"BlogReplies\\\">\\n\" +\n            \"  <strong><a href=\\\"http://my.oschina.net/flashsword/admin/blog-comments\\\" class=\\\"more\\\">管理&raquo;</a> 最新评论</strong>  \\n\" +\n            \"      <ul>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/flashsword\\\">@黄亿华</a>：引用来自“lidongyang”的评论 引用来自“黄亿华...\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275640366&type=18&user=190591\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/lidongyang\\\">@lidongyang</a>：引用来自“黄亿华”的评论 引用来自“lidongyan...\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275640301&type=18&user=723383\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/flashsword\\\">@黄亿华</a>：引用来自“lidongyang”的评论 引用来自“黄亿华...\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275638563&type=18&user=190591\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/lidongyang\\\">@lidongyang</a>：引用来自“黄亿华”的评论 引用来自“lidongyan...\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275638070&type=18&user=723383\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/flashsword\\\">@黄亿华</a>：引用来自“searchjack”的评论 不是好的就会被认...\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275617319&type=18&user=190591\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/searchjack\\\">@searchjack</a>：不是好的就会被认可， 干自己的， 到时候， 单干\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275617235&type=18&user=234880\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/searchjack\\\">@searchjack</a>：极好的工具，\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275616963&type=18&user=234880\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/flashsword\\\">@黄亿华</a>：引用来自“静风流云”的评论 貌似，OSC也是类似处...\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275599170&type=18&user=190591\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/rox\\\">@静风流云</a>：貌似，OSC也是类似处理的。\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275599137&type=18&user=180\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t<li>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/flashsword\\\">@黄亿华</a>：引用来自“仪山湖”的评论 最近要写个爬虫，看了...\\n\" +\n            \"\\t\\t<a href=\\\"/action/tweet/go?obj=275570030&type=18&user=190591\\\">查看&raquo;</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t  </ul>\\n\" +\n            \"  </div>\\n\" +\n            \"<div class='Mod' id='Stat'>\\n\" +\n            \"<strong>访客统计</strong>\\n\" +\n            \"<ul>\\n\" +\n            \"\\t<li><label>今日访问：</label>6 (<a href=\\\"http://my.oschina.net/flashsword/visitors\\\">查看最新访客&raquo;</a>)</li>\\n\" +\n            \"    <li><label>昨日访问：</label>284</li>\\n\" +\n            \"    <li><label>本周访问：</label>817</li>\\n\" +\n            \"    <li><label>本月访问：</label>1888</li>\\n\" +\n            \"    <li><label>所有访问：</label>16453</li>\\n\" +\n            \"</ul>\\n\" +\n            \"</div></div>\\n\" +\n            \"\\n\" +\n            \"<div class='SpaceList'>\\n\" +\n            \"\\t<div class='TopBar'>\\n\" +\n            \"    \\t<div class='NavPath'>\\t\\t\\n\" +\n            \"    \\t\\t<a href='http://my.oschina.net/flashsword'>空间</a> &raquo; <a href='http://my.oschina.net/flashsword/blog'>博客</a>\\t\\t\\t\\n\" +\n            \"\\t\\t\\t&raquo; <a href=\\\"http://my.oschina.net/flashsword/blog?catalog=371362\\\">webmagic</a>\\n\" +\n            \"\\t\\t\\t&raquo; 博客正文\\n\" +\n            \"    \\t</div>\\n\" +\n            \"\\t</div>\\n\" +\n            \"\\t\\n\" +\n            \"    \\t<div class='BlogEntity'>\\t\\t\\n\" +\n            \"      <div class='BlogTitle'>\\n\" +\n            \"        <h1><img src='/img/space/b1.gif' align='absmiddle'/> Jsoup代码解读之八-防御XSS攻击</h1>\\n\" +\n            \"        <div class='BlogStat'>\\n\" +\n            \"    \\t\\t    \\t\\t    \\t\\t<span class='admin'>\\n\" +\n            \"    \\t\\t\\t<a href=\\\"http://my.oschina.net/flashsword/admin/edit-blog?blog=158200\\\">编辑</a>&nbsp;|&nbsp;<a href=\\\"javascript:delete_blog(158200)\\\">删除</a>\\n\" +\n            \"    \\t\\t</span>\\n\" +\n            \"\\t\\t\\t    \\t\\t    \\t\\t发表于3天前(2013-08-31 08:24) , \\n\" +\n            \"    \\t\\t已有<strong>1628</strong>次阅读 ，共<strong><a href=\\\"#comments\\\">3</a></strong>个评论\\n\" +\n            \"    \\t\\t\\t\\t\\t，共 <strong>79</strong> 人收藏此文    \\t</div> \\n\" +\n            \"      </div>\\n\" +\n            \"\\t  \\t            <div class=\\\"BlogAnchor\\\">\\n\" +\n            \"            <p>目录：[ <strong><a href=\\\"#\\\" id=\\\"AnchorContentToggle\\\" title=\\\"收起\\\">-</a></strong> ]</p>\\n\" +\n            \"            <div class=\\\"AnchorContent\\\" id=\\\"AnchorContent\\\"><li class='osc_h2'><a href='#OSC_h2_1'>防御XSS攻击的一般原理</a></li><li class='osc_h2'><a href='#OSC_h2_2'>Cleaner与Whitelist</a></li><li class='osc_h2'><a href='#OSC_h2_3'>结束语</a></li></div>\\n\" +\n            \"    \\t  </div>\\n\" +\n            \"          <script>\\n\" +\n            \"\\t\\t  \\t$(function(){\\n\" +\n            \"\\t\\t\\t\\t$(\\\"#AnchorContentToggle\\\").click(function(){\\n\" +\n            \"\\t\\t\\t\\t\\tvar text = $(this).html();\\n\" +\n            \"\\t\\t\\t\\t\\tif(text==\\\"-\\\"){\\n\" +\n            \"\\t\\t\\t\\t\\t\\t$(this).html(\\\"+\\\");\\n\" +\n            \"\\t\\t\\t\\t\\t\\t$(this).attr({\\\"title\\\":\\\"展开\\\"});\\n\" +\n            \"\\t\\t\\t\\t\\t}else{\\n\" +\n            \"\\t\\t\\t\\t\\t\\t$(this).html(\\\"-\\\");\\n\" +\n            \"\\t\\t\\t\\t\\t\\t$(this).attr({\\\"title\\\":\\\"收起\\\"});\\n\" +\n            \"\\t\\t\\t\\t\\t}\\n\" +\n            \"\\t\\t\\t\\t\\t$(\\\"#AnchorContent\\\").toggle();\\n\" +\n            \"\\t\\t\\t\\t});\\n\" +\n            \"\\t\\t\\t});\\n\" +\n            \"\\t\\t  </script>\\n\" +\n            \"\\t  \\t  <div class='BlogContent'><p><img src=\\\"http://static.oschina.net/uploads/space/2013/0831/071752_RBZc_190591.png\\\" /></p> \\n\" +\n            \"<span id=\\\"OSC_h2_1\\\"></span>\\n\" +\n            \"<h2>防御XSS攻击的一般原理</h2> \\n\" +\n            \"<p>cleaner是Jsoup的重要功能之一，我们常用它来进行富文本输入中的XSS防御。</p> \\n\" +\n            \"<p>我们知道，XSS攻击的一般方式是，通过在页面输入中嵌入一段恶意脚本，对输出时的DOM结构进行修改，从而达到执行这段脚本的目的。对于纯文本输入，过滤/转义HTML特殊字符<code>&lt;</code>,<code>&gt;</code>,<code>&quot;</code>,<code>'</code>是行之有效的办法，但是如果本身用户输入的就是一段HTML文本(例如博客文章)，这种方式就不太有效了。这个时候，就是Jsoup大显身手的时候了。</p> \\n\" +\n            \"<p>在前面，我们已经知道了，Jsoup里怎么将HTML变成一棵DOM树，怎么对DOM树进行遍历，怎么对DOM文档进行输出，那么其实cleaner的实现方式，也能猜出大概了。使用Jsoup进行XSS防御，大致分为三个步骤:</p> \\n\" +\n            \"<ol> \\n\" +\n            \" <li><p>将HTML解析为DOM树</p> <p>这一步可以过滤掉一些企图搞破坏的非闭合标签、非正常语法等。例如一些输入，会尝试用<code>&lt;/textarea&gt;</code>闭合当前Tag，然后写入攻击脚本。而根据前面对Jsoup的parser的分析，这种时候，这些非闭合标签会被当做错误并丢弃。</p></li> \\n\" +\n            \" <li><p>过滤高风险标签/属性/属性值</p> <p>高风险标签是指<code>&lt;script&gt;</code>以及类似标签，对属性/属性值进行过滤是因为某些属性值里也可以写入javascript脚本，例如<code>onclick='alert(&quot;xss!&quot;)'</code>。</p></li> \\n\" +\n            \" <li><p>重新将DOM树输出为HTML文本</p> <p>DOM树的输出，在前面(Jsoup代码解读之三)已经提到过了。</p></li> \\n\" +\n            \"</ol> \\n\" +\n            \"<span id=\\\"OSC_h2_2\\\"></span>\\n\" +\n            \"<h2>Cleaner与Whitelist</h2> \\n\" +\n            \"<p>对于上述的两个步骤，1、3都已经分别在parser和输出中完成，现在只剩下步骤 2：过滤高风险标签等。</p> \\n\" +\n            \"<p>Jsoup给出的答案是白名单。下面是<code>Whitelist</code>的部分代码。</p> \\n\" +\n            \"<pre class=\\\"brush: java\\\">public class Whitelist {\\n\" +\n            \"    private Set&lt;TagName&gt; tagNames; // tags allowed, lower case. e.g. [p, br, span]\\n\" +\n            \"    private Map&lt;TagName, Set&lt;AttributeKey&gt;&gt; attributes; // tag -&gt; attribute[]. allowed attributes [href] for a tag.\\n\" +\n            \"    private Map&lt;TagName, Map&lt;AttributeKey, AttributeValue&gt;&gt; enforcedAttributes; // always set these attribute values\\n\" +\n            \"    private Map&lt;TagName, Map&lt;AttributeKey, Set&lt;Protocol&gt;&gt;&gt; protocols; // allowed URL protocols for attributes\\n\" +\n            \"    private boolean preserveRelativeLinks; // option to preserve relative links\\n\" +\n            \"}</pre> \\n\" +\n            \"<p>这里定义了标签名/属性名/属性值的白名单。</p> \\n\" +\n            \"<p>而<code>Cleaner</code>是过滤的执行者。不出所料，Cleaner内部定义了<code>CleaningVisitor</code>来进行标签的过滤。CleaningVisitor的过滤过程并不改变原始DOM树的值，而是将符合条件的属性，加入到<code>Element destination</code>里去。</p> \\n\" +\n            \"<pre class=\\\"brush: java\\\">private final class CleaningVisitor implements NodeVisitor {\\n\" +\n            \"    private int numDiscarded = 0;\\n\" +\n            \"    private final Element root;\\n\" +\n            \"    private Element destination; // current element to append nodes to\\n\" +\n            \"\\n\" +\n            \"    private CleaningVisitor(Element root, Element destination) {\\n\" +\n            \"        this.root = root;\\n\" +\n            \"        this.destination = destination;\\n\" +\n            \"    }\\n\" +\n            \"\\n\" +\n            \"    public void head(Node source, int depth) {\\n\" +\n            \"        if (source instanceof Element) {\\n\" +\n            \"            Element sourceEl = (Element) source;\\n\" +\n            \"\\n\" +\n            \"            if (whitelist.isSafeTag(sourceEl.tagName())) { // safe, clone and copy safe attrs\\n\" +\n            \"                ElementMeta meta = createSafeElement(sourceEl);\\n\" +\n            \"                Element destChild = meta.el;\\n\" +\n            \"                destination.appendChild(destChild);\\n\" +\n            \"\\n\" +\n            \"                numDiscarded += meta.numAttribsDiscarded;\\n\" +\n            \"                destination = destChild;\\n\" +\n            \"            } else if (source != root) { // not a safe tag, so don't add. don't count root against discarded.\\n\" +\n            \"                numDiscarded++;\\n\" +\n            \"            }\\n\" +\n            \"        } else if (source instanceof TextNode) {\\n\" +\n            \"            TextNode sourceText = (TextNode) source;\\n\" +\n            \"            TextNode destText = new TextNode(sourceText.getWholeText(), source.baseUri());\\n\" +\n            \"            destination.appendChild(destText);\\n\" +\n            \"        } else { // else, we don't care about comments, xml proc instructions, etc\\n\" +\n            \"            numDiscarded++;\\n\" +\n            \"        }\\n\" +\n            \"    }\\n\" +\n            \"\\n\" +\n            \"    public void tail(Node source, int depth) {\\n\" +\n            \"        if (source instanceof Element &amp;&amp; whitelist.isSafeTag(source.nodeName())) {\\n\" +\n            \"            destination = destination.parent(); // would have descended, so pop destination stack\\n\" +\n            \"        }\\n\" +\n            \"    }\\n\" +\n            \"}</pre> \\n\" +\n            \"<span id=\\\"OSC_h2_3\\\"></span>\\n\" +\n            \"<h2>结束语</h2> \\n\" +\n            \"<p>至此，Jsoup的全部模块都已经写完了。Jsoup源码并不多，只有14000多行，但是实现非常精巧，在读代码的过程中，除了相关知识，还验证几个很重要的思想：</p> \\n\" +\n            \"<ul> \\n\" +\n            \" <li><p>最好的代码抽象，是对现实概念的映射。</p> <p>这句话在看《代码大全》的时候印象很深刻。在Jsoup里，只要有相关知识，每个类的作用都能第一时间明白其作用。</p></li> \\n\" +\n            \" <li><p>不要过度抽象</p> <p>在Jsoup里，只用到了两个接口，一个是<code>NodeVisitor</code>，一个是<code>Connection</code>，其他都是用抽象类或者直接用实现类代替。记得有次面试的时候被问到我们开发中每逢一个功能，都要先定义一个接口的做法是否必要？现在的答案是没有必要，过度的抽象反而会降低代码质量。</p> <p>另外，Jsoup的代码内聚性都很高，每个类的功能基本都定义在类的内部，这是一个典型的充血模型。同时有大量的facade使用，而避免了Factory、Configure等类的出现，个人感觉这点是非常好的。</p></li> \\n\" +\n            \"</ul> \\n\" +\n            \"<p>最后继续贴上Jsoup解读系列的github地址：<a href=\\\"https://github.com/code4craft/jsoup-learning/\\\" rel=\\\"nofollow\\\">https://github.com/code4craft/jsoup-learning/</a></p></div>\\n\" +\n            \"      \\t  \\t  \\n\" +\n            \"      \\t\\n\" +\n            \"\\t        <div class='BlogTags'>\\n\" +\n            \"    \\t<strong>关键字：</strong>\\n\" +\n            \"    \\t    \\t<a href=\\\"http://www.oschina.net/search?scope=blog&q=Jsoup\\\" class=\\\"tag\\\">Jsoup</a>\\n\" +\n            \"    \\t    \\t<a href=\\\"http://www.oschina.net/search?scope=blog&q=XSS\\\" class=\\\"tag\\\">XSS</a>\\n\" +\n            \"    \\t    \\t<a href=\\\"http://www.oschina.net/search?scope=blog&q=OO\\\" class=\\\"tag\\\">OO</a>\\n\" +\n            \"    \\t    \\t      </div>\\n\" +\n            \"\\t  \\t  \\n\" +\n            \"      <div class='BlogCopyright'>\\t\\t\\n\" +\n            \"\\t  \\t\\t声明：OSCHINA 博客文章版权属于作者，受法律保护。未经作者同意不得转载。\\n\" +\n            \"\\t  \\t  </div>\\n\" +\n            \"\\n\" +\n            \"      <div class='BlogLinks'>\\n\" +\n            \"    \\t<ul>\\n\" +\n            \"                <li class='prev'><a href=\\\"http://my.oschina.net/flashsword/blog/158171\\\" title=\\\"上一篇：Jsoup代码解读之七-实现一个CSS Selector\\\">&laquo; Jsoup代码解读之七-实现一个CSS Selector</a></li>            \\t</ul>\\n\" +\n            \"\\t\\t      </div>\\n\" +\n            \"\\t</div>\\n\" +\n            \"\\n\" +\n            \"\\t<style type='text/css'>\\n\" +\n            \"\\t#BlogShare strong{float:left;padding-top:10px;font-size:11pt;color:#444;}\\n\" +\n            \"\\t#BlogShare a.share_sina{float:left;width:32px;height:32px;background:url('/img/icon01.gif') center no-repeat;}\\n\" +\n            \"\\t#BlogShare a.share_qq{float:left;width:32px;height:32px;margin-left: 10px;background:url('/img/icon02.gif') center no-repeat;}\\n\" +\n            \"\\t</style>\\n\" +\n            \"\\t<div class='BlogShare'>\\n\" +\n            \"\\t\\n\" +\n            \"\\t<span id='BlogShare'>\\n\" +\n            \"\\t\\t<strong>分享到： </strong>\\n\" +\n            \"\\t\\t<a class=\\\"share_sina\\\" title=\\\"分享到新浪微博\\\" href=\\\"javascript:void((function(s,d,e,r,l,p,t,z,c){var%20f='http://v.t.sina.com.cn/share/share.php?appkey=858381728',u=z||d.location,p=['&url=',e(u),'&title=',e(t||d.title),'&source=',e(r),'&sourceUrl=',e(l),'&content=',c||'gb2312','&pic=',e(p||'')].join('');function%20a(){if(!window.open([f,p].join(''),'mb',['toolbar=0,status=0,resizable=1,width=440,height=430,left=',(s.width-440)/2,',top=',(s.height-430)/2].join('')))u.href=[f,p].join('');};if(/Firefox/.test(navigator.userAgent))setTimeout(a,0);else%20a();})(screen,document,encodeURIComponent,'','','','Jsoup代码解读之八-防御XSS攻击: 防御XSS攻击的一般原理 cleaner是Jsoup的重要功能之一，我们常用它来进行富文本输入中的XSS防御。 我们知道，XSS攻击的一般方式是，通过在页面输入中嵌入一段恶意脚本，...','','utf-8'));\\\"></a>\\n\" +\n            \"\\t\\t<a class=\\\"share_qq\\\" title=\\\"分享到腾讯微博\\\" href=\\\"javascript:(function(){window.open('http://v.t.qq.com/share/share.php?url='+encodeURIComponent(document.location)+'&amp;appkey=96f54f97c4de46e393c4835a266207f4&amp;site=&amp;title='+encodeURIComponent(document.title)+encodeURIComponent(': 防御XSS攻击的一般原理 cleaner是Jsoup的重要功能之一，我们常用它来进行富文本输入中的XSS防御。 我们知道，XSS攻击的一般方式是，通过在页面输入中嵌入一段恶意脚本，...'),'', 'width=450, height=400, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=yes, resizable=no, status=no');}())\\\"></a></span>\\n\" +\n            \"\\t<span id='BlogVote'>\\n\" +\n            \"    <a href=\\\"javascript:vote(158200)\\\">顶</a><span>已有 <em id='vote_count'>0</em>人顶</span>\\n\" +\n            \"\\t</span>\\n\" +\n            \"\\t</div>\\n\" +\n            \"\\t\\t\\n\" +\n            \"</div>\\n\" +\n            \"<div class='SpaceList' style='margin-top:20px;'>\\n\" +\n            \"<div class='BlogComments'>\\n\" +\n            \"    <h2><a name=\\\"comments\\\"></a>共有 3 条网友评论</h2>\\n\" +\n            \"\\t\\t\\t<ul id=\\\"BlogComments\\\">\\n\" +\n            \"\\t\\t\\t\\t\\t\\t<li id='cmt_158200_180_275599137'>\\n\" +\n            \"\\t<table class='ostable'><tr>\\n\" +\n            \"\\t<td class='portrait'>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/rox\\\" target=\\\"_blank\\\"><img src=\\\"http://static.oschina.net/uploads/user/0/180_50.jpg?t=1367919013000\\\" align=\\\"absmiddle\\\" alt=\\\"静风流云\\\" title=\\\"静风流云\\\" class=\\\"SmallPortrait\\\" user=\\\"180\\\"/></a>\\t\\t\\t\\n\" +\n            \"\\t</td>\\n\" +\n            \"\\t<td class='body'>\\n\" +\n            \"\\t\\t<div class='title'>\\n\" +\n            \"\\t\\t\\t1楼：<a href=\\\"http://my.oschina.net/rox\\\" target=\\\"_blank\\\" name=\\\"rpl_275599137\\\">静风流云</a> 发表于 2013-09-01 08:34    \\t\\t\\t\\n\" +\n            \"        \\t        \\t  <a href=\\\"javascript:delete_c(158200,180,275599137)\\\">删除</a>\\n\" +\n            \"\\t\\t\\t\\t\\t\\t\\t\\t\\t  <a href=\\\"javascript:ReplyInline(158200,180,275599137)\\\">回复此评论</a>\\n\" +\n            \"\\t\\t\\t\\t\\t</div>\\n\" +\n            \"\\t\\t<div class='post'\\\">貌似，OSC也是类似处理的。</div>\\n\" +\n            \"\\t\\t<div id='inline_reply_of_158200_180_275599137' class='inline_reply'></div>\\n\" +\n            \"    </td>\\n\" +\n            \"\\t</tr></table>\\n\" +\n            \"</li>\\t\\t\\t\\t\\t<li id='cmt_158200_190591_275599170'>\\n\" +\n            \"\\t<table class='ostable'><tr>\\n\" +\n            \"\\t<td class='portrait'>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/flashsword\\\" target=\\\"_blank\\\"><img src=\\\"http://static.oschina.net/uploads/user/95/190591_50.jpg?t=1347254905000\\\" align=\\\"absmiddle\\\" alt=\\\"黄亿华\\\" title=\\\"黄亿华\\\" class=\\\"SmallPortrait\\\" user=\\\"190591\\\"/></a>\\t\\t\\t\\n\" +\n            \"\\t</td>\\n\" +\n            \"\\t<td class='body'>\\n\" +\n            \"\\t\\t<div class='title'>\\n\" +\n            \"\\t\\t\\t2楼：<a href=\\\"http://my.oschina.net/flashsword\\\" target=\\\"_blank\\\" name=\\\"rpl_275599170\\\">黄亿华</a> 发表于 2013-09-01 08:37    \\t\\t\\t\\n\" +\n            \"        \\t        \\t  <a href=\\\"javascript:delete_c(158200,190591,275599170)\\\">删除</a>\\n\" +\n            \"\\t\\t\\t\\t\\t\\t\\t\\t</div>\\n\" +\n            \"\\t\\t<div class='post'\\\"><div class=ref><h4>引用来自“静风流云”的评论</h4><p>貌似，OSC也是类似处理的。</p></div>OSC就是使用Jsoup做解析的，见这里：<a href='http://www.oschina.net/p/jsoup' rel='nofollow' target='_blank'>http://www.oschina.net/p/jsoup</a></div>\\n\" +\n            \"\\t\\t<div id='inline_reply_of_158200_190591_275599170' class='inline_reply'></div>\\n\" +\n            \"    </td>\\n\" +\n            \"\\t</tr></table>\\n\" +\n            \"</li>\\t\\t\\t\\t\\t<li id='cmt_158200_234880_275616963'>\\n\" +\n            \"\\t<table class='ostable'><tr>\\n\" +\n            \"\\t<td class='portrait'>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/searchjack\\\" target=\\\"_blank\\\"><img src=\\\"http://static.oschina.net/uploads/user/117/234880_50.jpg?t=1362718646000\\\" align=\\\"absmiddle\\\" alt=\\\"searchjack\\\" title=\\\"searchjack\\\" class=\\\"SmallPortrait\\\" user=\\\"234880\\\"/></a>\\t\\t\\t\\n\" +\n            \"\\t</td>\\n\" +\n            \"\\t<td class='body'>\\n\" +\n            \"\\t\\t<div class='title'>\\n\" +\n            \"\\t\\t\\t3楼：<a href=\\\"http://my.oschina.net/searchjack\\\" target=\\\"_blank\\\" name=\\\"rpl_275616963\\\">searchjack</a> 发表于 2013-09-02 09:20    \\t\\t\\t\\n\" +\n            \"        \\t        \\t  <a href=\\\"javascript:delete_c(158200,234880,275616963)\\\">删除</a>\\n\" +\n            \"\\t\\t\\t\\t\\t\\t\\t\\t\\t  <a href=\\\"javascript:ReplyInline(158200,234880,275616963)\\\">回复此评论</a>\\n\" +\n            \"\\t\\t\\t\\t\\t</div>\\n\" +\n            \"\\t\\t<div class='post'\\\">极好的工具，</div>\\n\" +\n            \"\\t\\t<div id='inline_reply_of_158200_234880_275616963' class='inline_reply'></div>\\n\" +\n            \"    </td>\\n\" +\n            \"\\t</tr></table>\\n\" +\n            \"</li>\\t\\t\\t\\t</ul>\\n\" +\n            \"</div>\\n\" +\n            \"\\t</div>\\n\" +\n            \"\\n\" +\n            \"<div id='inline_reply_editor' style='display:none;'>\\n\" +\n            \"<div class=\\\"BlogCommentForm\\\">\\n\" +\n            \"\\t<form id=\\\"form_inline_comment\\\" action=\\\"/action/blog/add_comment?blog=158200\\\" method=\\\"POST\\\">\\n\" +\n            \"\\t  <input type='hidden' id='inline_reply_id' name='reply_id' value=''/>          \\n\" +\n            \"      <textarea name=\\\"content\\\" style=\\\"width:550px;height:60px;\\\" onkeydown=\\\"if((event.metaKey || event.ctrlKey)&&event.keyCode==13){$('#form_inline_comment').submit();}\\\"></textarea><br>\\n\" +\n            \"\\t  <input type=\\\"submit\\\" value=\\\"回复\\\" id=\\\"btn_comment\\\" class=\\\"SUBMIT\\\"/> \\n\" +\n            \"\\t  <input type=\\\"button\\\" value=\\\"关闭\\\" class=\\\"SUBMIT\\\" id='btn_close_inline_reply'/> 文明上网，理性发言\\n\" +\n            \"    </form>\\n\" +\n            \"</div>\\n\" +\n            \"</div>\\n\" +\n            \"<div class='SpaceList' style='margin-top:20px;'>\\n\" +\n            \"  <a name=\\\"comments\\\" id=\\\"postform\\\"></a>\\n\" +\n            \"    <div class=\\\"BlogCommentForm\\\">\\n\" +\n            \"    <form id=\\\"form_comment\\\" action=\\\"/action/blog/add_comment?blog=158200\\\" method=\\\"POST\\\">          \\n\" +\n            \"      <textarea id='ta_post_content' name=\\\"content\\\" style=\\\"width:550px;height:100px;\\\" onkeydown=\\\"if((event.metaKey || event.ctrlKey)&&event.keyCode==13){$('#form_comment').submit();}\\\"></textarea><br>\\n\" +\n            \"\\t  <input type=\\\"submit\\\" value=\\\"发表评论\\\" id=\\\"btn_comment\\\" class=\\\"SUBMIT\\\" /> \\n\" +\n            \"\\t  <img id=\\\"submiting\\\" style=\\\"display:none\\\" src=\\\"/img/loading.gif\\\" align=\\\"absmiddle\\\"/>\\n\" +\n            \"\\t  <span id='cmt_tip'>文明上网，理性发言</span>\\n\" +\n            \"    </form>\\n\" +\n            \"\\t<a href=\\\"#\\\" class=\\\"more\\\">回到页首</a>&nbsp;|&nbsp;<a href=\\\"#comments\\\" class=\\\"more\\\">回到评论列表</a>\\n\" +\n            \"  </div>\\n\" +\n            \"  </div>\\n\" +\n            \"\\t\\n\" +\n            \"<div id=\\\"RelativeBlogs\\\">\\n\" +\n            \"\\t<strong><a id='btn_close'>关闭</a>相关文章阅读</strong>\\n\" +\n            \"\\t<ul>\\n\" +\n            \"\\t\\t\\t<li>\\n\" +\n            \"\\t\\t<span class='date'>2012/04/04</span>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/soitravel/blog/52366\\\" title=\\\"oo原则\\\">oo原则</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t\\t\\t<li>\\n\" +\n            \"\\t\\t<span class='date'>2012/09/03</span>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/wangfree/blog/76273\\\" title=\\\"XSS跨站脚本攻击\\\">XSS跨站脚本攻击</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t\\t\\t<li>\\n\" +\n            \"\\t\\t<span class='date'>2012/10/10</span>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/samshuai/blog/82382\\\" title=\\\"《蟋蟀的xss淫荡教程之如何劫持OSC用户账号》\\\">《蟋蟀的xss淫荡教程之如何劫持OSC...</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t\\t\\t<li>\\n\" +\n            \"\\t\\t<span class='date'>2013/06/08</span>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/tdoly/blog/136632\\\" title=\\\"[Security]XSS一直是个棘手的问题\\\">[Security]XSS一直是个棘手的问题...</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t\\t\\t<li>\\n\" +\n            \"\\t\\t<span class='date'>2013/01/05</span>\\n\" +\n            \"\\t\\t<a href=\\\"http://my.oschina.net/sharephper/blog/100107\\\" title=\\\"xss攻击\\\">xss攻击</a>\\n\" +\n            \"\\t</li>\\n\" +\n            \"\\t\\t\\t</ul>\\n\" +\n            \"</div>\\n\" +\n            \"<script type=\\\"text/javascript\\\" src=\\\"/action/visit/blog?id=158200\\\" defer=\\\"defer\\\"></script>\\n\" +\n            \"<script type=\\\"text/javascript\\\" src=\\\"/js/syntax-highlighter-2.1.382/scripts/brush.js\\\"></script>\\n\" +\n            \"<link type=\\\"text/css\\\" rel=\\\"stylesheet\\\" href=\\\"/js/syntax-highlighter-2.1.382/styles/shCore.css\\\"/>\\n\" +\n            \"<link type=\\\"text/css\\\" rel=\\\"stylesheet\\\" href=\\\"/js/syntax-highlighter-2.1.382/styles/shThemeDefault.css\\\"/>\\n\" +\n            \"<script type='text/javascript'><!--\\n\" +\n            \"$(document).ready(function(){\\n\" +\n            \"\\tSyntaxHighlighter.config.clipboardSwf = '/js/syntax-highlighter-2.1.382/scripts/clipboard.swf';\\n\" +\n            \"\\tSyntaxHighlighter.all();\\n\" +\n            \"});\\n\" +\n            \"//-->\\n\" +\n            \"</script>\\n\" +\n            \"<!--[if lt IE 7]>\\n\" +\n            \"<script type=\\\"text/javascript\\\" src=\\\"/js/minmax.js\\\"></script>\\n\" +\n            \"<![endif]-->\\n\" +\n            \"<script type='text/javascript'>\\n\" +\n            \"<!--\\n\" +\n            \"var posting = false;\\n\" +\n            \"var upprev_closed = false;\\n\" +\n            \"var upprev_hidden = true;\\n\" +\n            \"\\n\" +\n            \"$(document).ready(function(){\\n\" +\n            \"    $('.BlogContent img').css('cursor','pointer');\\n\" +\n            \"    jQuery.each($('.BlogContent img'),function(idx,v){\\n\" +\n            \"    \\t$(v).wrap(\\\"<a href='\\\"+$(this).attr('src')+\\\"' target='_blank'></a>\\\");\\n\" +\n            \"    });\\n\" +\n            \"\\t$('#form_comment').ajaxForm({\\n\" +\n            \"\\t\\tdataType: 'json',\\n\" +\n            \"\\t\\tbforeSubmit: function(){\\n\" +\n            \"\\t\\t\\tposting = true;\\n\" +\n            \"\\t\\t},\\n\" +\n            \"\\t\\tsuccess: function(json) {\\n\" +\n            \"        \\tif(json.msg){\\n\" +\n            \"\\t\\t\\t\\t///alert(json.msg);\\n\" +\n            \"\\t\\t\\t\\t$('#cmt_tip').html(\\\"<span style='color:#C00;'>\\\"+json.msg+\\\"</span>\\\");\\n\" +\n            \"\\t\\t\\t\\t$('#ta_post_content').focus();\\t\\t\\t\\t\\n\" +\n            \"\\t\\t\\t}else{\\n\" +\n            \"\\t\\t\\t\\tvar url = \\\"http://my.oschina.net/flashsword/blog_post?_cmt_blog=\\\"+json.blog+\\\"&_cmt_user=\\\"+json.user+\\\"&_cmt_id=\\\"+json.id;\\t\\t\\t\\t\\n\" +\n            \"        \\t\\tjQuery.get(url, function(data){\\n\" +\n            \"    \\t\\t\\t\\t$('.BlogComments .NoData').hide();\\n\" +\n            \"        \\t\\t\\t$('ul#BlogComments').append(data);\\n\" +\n            \"        \\t\\t\\t$('#form_comment').resetForm();\\n\" +\n            \"        \\t\\t}); \\n\" +\n            \"\\t\\t\\t}\\n\" +\n            \"\\t\\t}\\n\" +\n            \"\\t});\\n\" +\n            \"\\n\" +\n            \"    var at_datas = [];\\n\" +\n            \"    $('img.SmallPortrait').each(function(){\\n\" +\n            \"        var name = $(this).attr('alt');\\n\" +\n            \"        if(jQuery.inArray(name, at_datas) < 0 && name != '黄亿华')\\n\" +\n            \"            at_datas.push(name);\\n\" +\n            \"    });\\n\" +\n            \"    $(\\\"#form_comment textarea\\\").atWho(\\\"@\\\", {data: at_datas});\\n\" +\n            \"\\n\" +\n            \"\\t$(\\\"#submiting\\\").ajaxStart(function(){\\n\" +\n            \"\\t   if(posting){\\n\" +\n            \"    \\t   $('#btn_submit').attr(\\\"disabled\\\",\\\"disabled\\\");\\n\" +\n            \"           $(this).show();\\n\" +\n            \"\\t   }\\n\" +\n            \"    });\\n\" +\n            \"\\t$(\\\"#submiting\\\").ajaxComplete(function(event,request, settings){\\n\" +\n            \"\\t   if(posting){\\n\" +\n            \"           $(this).hide();\\n\" +\n            \"    \\t   $('#btn_submit').attr(\\\"disabled\\\",\\\"\\\");\\n\" +\n            \"\\t   }\\n\" +\n            \"\\t   posting = false;\\n\" +\n            \"    }); \\n\" +\n            \"\\t\\n\" +\n            \"    $(window).scroll(function() {\\n\" +\n            \"        var lastScreen;\\n\" +\n            \"        if ($(\\\"#postform\\\").length > 0)\\n\" +\n            \"            lastScreen = getScrollY() + $(window).height() < $(\\\"#postform\\\").offset().top * 1 ? false : true;\\n\" +\n            \"        else\\n\" +\n            \"            lastScreen = getScrollY() + $(window).height() < $(document).height() * 1 ? false : true;\\n\" +\n            \"        if (lastScreen && !upprev_closed) {\\n\" +\n            \"            $(\\\"#RelativeBlogs\\\").stop().animate({right:\\\"0px\\\"});\\n\" +\n            \"            upprev_hidden = false;\\n\" +\n            \"        }\\n\" +\n            \"        else if (upprev_closed && getScrollY() == 0) {\\n\" +\n            \"            upprev_closed = false;\\n\" +\n            \"        }\\n\" +\n            \"        else if (!upprev_hidden) {\\n\" +\n            \"            upprev_hidden = true;\\n\" +\n            \"            $(\\\"#RelativeBlogs\\\").stop().animate({right:\\\"-400px\\\"});\\n\" +\n            \"        }\\n\" +\n            \"    });\\n\" +\n            \"    $(\\\"#RelativeBlogs #btn_close\\\").click(function() {\\n\" +\n            \"        $(\\\"#RelativeBlogs\\\").stop().animate({right:\\\"-400px\\\"});\\n\" +\n            \"        upprev_closed = true;\\n\" +\n            \"        upprev_hidden = true;\\n\" +\n            \"    });\\n\" +\n            \"});\\n\" +\n            \"function delete_c(nid,uid,cid){\\n\" +\n            \"  if(confirm(\\\"您确认要删除此篇评论？\\\")){\\n\" +\n            \"    var args = \\\"cmt=\\\"+cid+\\\"#\\\"+uid+\\\"#\\\"+nid;\\n\" +\n            \"    ajax_post(\\\"/action/blog/delete_blog_comments?space=190591\\\",args,function(){$(\\\"#cmt_\\\"+nid+\\\"_\\\"+uid+\\\"_\\\"+cid).fadeOut();});\\n\" +\n            \"  }\\n\" +\n            \"}\\n\" +\n            \"function ReplyInline(blog,user,reply){\\n\" +\n            \"\\t$('.inline_reply').empty();\\n\" +\n            \"\\tvar div_id = '#inline_reply_of_'+blog+'_'+user+'_'+reply;\\n\" +\n            \"\\t$('#inline_reply_id').val(user+'_'+reply);\\n\" +\n            \"\\t$(div_id).html($('#inline_reply_editor').html());\\n\" +\n            \"\\t$('#txt_focus').focus();\\n\" +\n            \"\\t$('#btn_close_inline_reply').click(function(){\\n\" +\n            \"\\t\\t$(div_id).empty();\\n\" +\n            \"\\t});\\n\" +\n            \"\\t$('#form_inline_comment').ajaxForm({\\n\" +\n            \"\\t\\tdataType: 'json',\\n\" +\n            \"    \\tsuccess: function(json) {\\n\" +\n            \"        \\tif(json.msg){\\n\" +\n            \"        \\t\\talert(json.msg);\\n\" +\n            \"        \\t}\\n\" +\n            \"        \\telse if(json.id){\\n\" +\n            \"    \\t\\t\\tlocation.reload();\\n\" +\n            \"        \\t}\\n\" +\n            \"    \\t}\\n\" +\n            \"\\t});\\n\" +\n            \"}\\n\" +\n            \"function edit_catalogs(qid){\\n\" +\n            \"\\tpopup(\\\"/set-catalogs?parent=1&type=3&id=\\\"+qid);\\n\" +\n            \"}\\n\" +\n            \"function vote(blogid){\\n\" +\n            \"\\t\\tajax_post(\\\"/action/blog/vote\\\",\\\"id=\\\"+blogid+\\\"&user=190591\\\",function(result){\\n\" +\n            \"\\t\\tvar json = eval('('+result+')');\\n\" +\n            \"\\t\\tif(json.vote)\\n\" +\n            \"\\t\\t\\t$('#vote_count').html(json.vote);\\n\" +\n            \"\\t\\telse if(json.error == 1)\\n\" +\n            \"\\t\\t\\talert(json.msg);\\n\" +\n            \"\\t\\telse\\n\" +\n            \"\\t\\t\\talert(json.msg);\\n\" +\n            \"\\t});\\n\" +\n            \"\\t}\\n\" +\n            \"function toggle_recomm(blogid){\\n\" +\n            \"\\tajax_post(\\\"/action/blog/toggle_recomm\\\",\\\"id=\\\"+blogid,function(html){\\n\" +\n            \"\\t\\tif(html == '-1')\\n\" +\n            \"\\t\\t\\talert(\\\"文章不存在\\\");\\n\" +\n            \"\\t\\telse if(html == 0){\\n\" +\n            \"\\t\\t\\t$('#lnk_recomm_'+blogid).removeClass('recommend');\\n\" +\n            \"\\t\\t\\t$('#lnk_recomm_'+blogid).text(\\\"未推荐\\\");\\n\" +\n            \"\\t\\t}\\n\" +\n            \"\\t\\telse if(html == 1){\\n\" +\n            \"\\t\\t\\t$('#lnk_recomm_'+blogid).addClass('recommend');\\n\" +\n            \"\\t\\t\\t$('#lnk_recomm_'+blogid).text(\\\"已推荐\\\");\\n\" +\n            \"\\t\\t}\\n\" +\n            \"\\t});\\n\" +\n            \"}\\n\" +\n            \"//-->\\n\" +\n            \"</script></div>\\n\" +\n            \"\\t<div class='clear'></div>\\n\" +\n            \"\\t<div id=\\\"OSC_Footer\\\"><style>\\n\" +\n            \".oscapp {text-align:left; width:220px;}\\n\" +\n            \".oscapp span {float:left;width:140px;}\\n\" +\n            \".oscapp a {float:left;text-indent:-9999em;width:16px;margin-left:8px;}\\n\" +\n            \".oscapp a.android {background:url('/img/android.gif') no-repeat left center;}\\n\" +\n            \".oscapp a.iphone {background:url('/img/iphone.gif') no-repeat left center;}\\n\" +\n            \".oscapp a.wp7 {background:url('/img/wp7.gif') no-repeat left center;}\\n\" +\n            \"</style>\\n\" +\n            \"<table width='100%'><tr>\\n\" +\n            \"<td align='left'>&copy; 开源中国(OsChina.NET) | <a href=\\\"http://www.oschina.net/home/about\\\">关于我们</a> | <a href=\\\"mailto:oschina.net@gmail.com\\\">广告联系</a> | <a href=\\\"http://weibo.com/oschina2010\\\" target=\\\"_blank\\\">@新浪微博</a> | <a href=\\\"http://m.oschina.net/\\\">开源中国手机版</a> | <a href='http://www.miitbeian.gov.cn/' target='_blank' style='color:#737573;text-decoration:none;'>粤ICP备12009483号-3</a></td>\\n\" +\n            \"<td class='oscapp'>\\n\" +\n            \"\\t<span>开源中国手机客户端：</span>\\n\" +\n            \"\\t<a href=\\\"http://www.oschina.net/app\\\" class='android' title='Android客户端'>Android</a>\\n\" +\n            \"\\t<a href=\\\"http://www.oschina.net/app\\\" class='iphone' title='iPhone 客户端'>iPhone</a>\\n\" +\n            \"\\t<a href=\\\"http://www.oschina.net/app\\\" class='wp7' title='Windows Phone 客户端'>WP7</a>\\n\" +\n            \"</td>\\n\" +\n            \"</tr>\\n\" +\n            \"</table>\\n\" +\n            \"<script type='text/javascript'>\\n\" +\n            \"<!--\\n\" +\n            \"if (top.location != self.location)top.location=self.location;\\n\" +\n            \"//-->\\n\" +\n            \"</script></div>\\n\" +\n            \"</div>\\n\" +\n            \"</body>\\n\" +\n            \"\\n\" +\n            \"<script type=\\\"text/javascript\\\" src=\\\"/action/visit/space?id=190591\\\"></script>\\n\" +\n            \"<script type='text/javascript'>\\n\" +\n            \"<!--\\n\" +\n            \"$(document).ready(function() {\\n\" +\n            \"\\n\" +\n            \"\\tSelectStyle(\\\"#search-item\\\",\\\".search_list\\\");\\n\" +\n            \"\\t$('.Tweet .photo img').live(\\\"click\\\",function(){\\n\" +\n            \"\\t\\tvar T=$(this);\\n\" +\n            \"\\t\\tvar t=this;\\n\" +\n            \"\\t\\tvar bigImg = T.attr('bi');\\n\" +\n            \"\\t\\tvar smallImg = T.attr('si');\\n\" +\n            \"\\t\\tvar src = T.attr('src');\\n\" +\n            \"\\t\\tvar newsrc = (bigImg == src)?smallImg:bigImg;\\n\" +\n            \"\\t\\tvar imgId = T.attr('id');\\n\" +\n            \"\\t\\tif(newsrc == bigImg){\\n\" +\n            \"    \\t\\tvar loading=$('<img alt=\\\"loading\\\" src=\\\"/img/loading.gif\\\"/>');\\n\" +\n            \"\\t\\t\\tvar top = T.position().top+T.height()/2-8;\\n\" +\n            \"\\t\\t\\tvar left = T.position().left+T.width()/2-8;\\n\" +\n            \"\\t\\t\\tloading.css({\\n\" +\n            \"\\t\\t\\t\\t'position':'absolute',\\n\" +\n            \"\\t\\t\\t\\t'z-index':999,\\n\" +\n            \"\\t\\t\\t\\t'top':top,\\n\" +\n            \"\\t\\t\\t\\t'left':left\\n\" +\n            \"\\t\\t\\t});\\n\" +\n            \"    \\t\\tT.before(loading);\\n\" +\n            \"\\t\\t\\tvar tImg=new Image();\\n\" +\n            \"\\t\\t\\ttImg.src=newsrc;\\n\" +\n            \"\\t\\t\\ttImg.onload=function(){afterImgLoad(T,loading,imgId,newsrc,bigImg);};\\n\" +\n            \"\\t\\t}\\n\" +\n            \"\\t\\telse{\\n\" +\n            \"\\t\\t\\tT.attr(\\\"src\\\",newsrc);\\n\" +\n            \"\\t\\t\\t$('#img_menu_'+imgId).remove();\\n\" +\n            \"\\t\\t}\\n\" +\n            \"\\t\\treturn false;\\n\" +\n            \"\\t});\\n\" +\n            \"\\t\\n\" +\n            \"\\t$(\\\".tweet_thumb_wrapper\\\").mouseenter(function(){\\n\" +\n            \"\\t\\t$(this).find(\\\".tweet_play_video\\\").css(\\\"opacity\\\",1);\\n\" +\n            \"\\t}).mouseleave(function(){\\n\" +\n            \"\\t\\t$(this).find(\\\".tweet_play_video\\\").css(\\\"opacity\\\",0.7);\\n\" +\n            \"\\t});\\n\" +\n            \"\\n\" +\n            \"    $(\\\"#TForm textarea\\\").atWho(\\\"@\\\", function(query, callback){\\n\" +\n            \"        jQuery.ajax({\\n\" +\n            \"            type:'POST',\\n\" +\n            \"            url:\\\"/action/tweet/at_suggest\\\",\\n\" +\n            \"            data:{'q':query},\\n\" +\n            \"            dataType:'json',\\n\" +\n            \"            success:function(json){\\n\" +\n            \"                callback(json);\\n\" +\n            \"            }\\n\" +\n            \"        });\\n\" +\n            \"    });\\n\" +\n            \"\\t\\n\" +\n            \"\\ttoggle_tweet_video = function(id){\\n\" +\n            \"\\t\\t$(\\\"#tweet_video_thumb_\\\"+id).toggle();\\n\" +\n            \"\\t\\tvar video = $(\\\"#tweet_video_\\\"+id).toggle();\\n\" +\n            \"\\t\\tvideo.siblings(\\\".tweet_video_operation,.tweet_thumb_wrapper\\\").toggle();\\n\" +\n            \"\\t};\\n\" +\n            \"\\t\\n\" +\n            \"\\tfunction afterImgLoad(T,loading,imgId,url,bigImg){\\n\" +\n            \"\\t\\tvar lnks = \\\"<div id='img_menu_\\\"+imgId+\\\"' class='ImgMenu'>\\\";\\n\" +\n            \"\\t\\tlnks += \\\"<a href='#' onclick='$(\\\\\\\"#\\\"+imgId+\\\"\\\\\\\").click();return false;'>收起</a>\\\";\\n\" +\n            \"\\t\\tlnks += \\\"<a href='\\\"+bigImg+\\\"' target='_blank'>查看原图</a></div>\\\";\\t\\t\\t\\n\" +\n            \"\\t\\tloading.remove();\\n\" +\n            \"\\t\\tT.attr(\\\"src\\\",url);\\n\" +\n            \"\\t\\tT.before(lnks);\\n\" +\n            \"\\t}\\n\" +\n            \"});\\n\" +\n            \"\\n\" +\n            \"function set_fellow_memo(fid,fname){\\n\" +\n            \"\\tpopup(\\\"/action/ajax/set_fellow_memo\\\",\\\"friend=\\\"+fid+\\\"&name=\\\"+fname);\\n\" +\n            \"}\\n\" +\n            \"\\n\" +\n            \"function deleteMsgs(uid, fid, fname){\\n\" +\n            \"\\tif(!confirm(\\\"你确认要清除与‘\\\"+fname+\\\"’的所有留言信息吗？\\\"))\\n\" +\n            \"\\t\\treturn ;\\n\" +\n            \"\\tvar args = \\\"user=\\\"+uid+\\\"&friend=\\\"+fid;\\n\" +\n            \"\\tajax_post(\\\"/action/msg/delete_user\\\",args,function(html){\\n\" +\n            \"\\t\\tif(html.length > 0)\\n\" +\n            \"\\t\\t\\talert(html);\\n\" +\n            \"\\t\\telse{\\n\" +\n            \"\\t\\t\\t$('#Msg_'+fid).fadeOut();\\n\" +\n            \"\\t\\t}\\n\" +\n            \"\\t});\\n\" +\n            \"}\\n\" +\n            \"\\n\" +\n            \"function follow_user(uid, uname){\\n\" +\n            \"\\tjust_follow(uid, uname,'190591'); //oschina.js\\n\" +\n            \"}\\n\" +\n            \"\\n\" +\n            \"function unfollow_user(uid, uname){\\n\" +\n            \"\\tif(confirm(\\\"确定不再关注\\\" + uname + \\\"了吗？\\\"))\\n\" +\n            \"\\tjust_unfollow(uid,'190591',function(){\\n\" +\n            \"\\t\\talert('已取消对 ' + uname + ' 的关注');\\n\" +\n            \"\\t});\\n\" +\n            \"}\\n\" +\n            \"\\n\" +\n            \"function tweet_reply(logid){\\n\" +\n            \"\\tvar r = $('#LogReply_'+logid);\\n\" +\n            \"\\tif(!r.is(\\\":hidden\\\")){\\n\" +\n            \"\\t\\tclose_tweet_reply(logid);\\n\" +\n            \"\\t\\treturn ;\\n\" +\n            \"\\t}\\n\" +\n            \"\\tr.html(\\\"<div class='TweetRplsWrapper'><span class='loading'>正在加载评论，请稍候...</span></div>\\\")\\n\" +\n            \"\\tr.show();\\n\" +\n            \"\\tr.load(\\\"http://my.oschina.net/flashsword/tweet-rpls?log=\\\"+logid,function(){\\n\" +\n            \"\\t\\t$('#edt_tweet_post_'+logid).focus();\\n\" +\n            \"        var at_datas = [];\\n\" +\n            \"        $(this).find(\\\"img.SmallPortrait\\\").each(function(){\\n\" +\n            \"            var name = $(this).attr('alt');\\n\" +\n            \"            if(jQuery.inArray(name, at_datas) < 0 && name != '黄亿华')\\n\" +\n            \"                at_datas.push(name);\\n\" +\n            \"        });\\n\" +\n            \"        $(this).find(\\\"input.TXT_TweetRpl_Text\\\").atWho(\\\"@\\\", {data: at_datas});\\n\" +\n            \"        $('#TweetReplyForm_'+logid).ajaxForm({\\n\" +\n            \"        \\tdataType: 'json',\\n\" +\n            \"\\t\\t\\tbeforeSubmit: function(arr, form, options){\\n\" +\n            \"\\t\\t\\t\\t$('#BTN_TweetReply_'+logid).attr('disabled','disabled');\\n\" +\n            \"\\t\\t\\t},\\n\" +\n            \"            success: function(json) {\\n\" +\n            \"            \\tif(json.msg){\\n\" +\n            \"        \\t\\t\\talert(json.msg);\\n\" +\n            \"            \\t}else if(json.log){\\n\" +\n            \"\\t\\t\\t\\t\\t$('#log_reply_count_'+logid).text(json.reply_count);\\n\" +\n            \"        \\t\\t\\t//插入新评论\\t\\t\\t\\t\\t\\n\" +\n            \"\\t\\t\\t\\t\\tajax_get(\\\"/action/ajax/get_tweet_reply?id=\\\" + json.log,true,function(html){\\n\" +\n            \"\\t\\t\\t\\t\\t\\t$('#LogReply_'+logid+' ul').prepend(html);\\n\" +\n            \"\\t\\t\\t\\t\\t});\\n\" +\n            \"\\t\\t\\t\\t\\t$('#edt_tweet_post_'+logid).val('');\\n\" +\n            \"            \\t}\\n\" +\n            \"\\t\\t\\t\\t$('#BTN_TweetReply_'+logid).removeAttr('disabled');\\n\" +\n            \"            }\\n\" +\n            \"        });\\n\" +\n            \"\\t});\\n\" +\n            \"}\\n\" +\n            \"function close_tweet_reply(logid){\\n\" +\n            \"\\t$('#LogReply_'+logid).empty();\\n\" +\n            \"\\t$('#LogReply_'+logid).hide();\\n\" +\n            \"\\t$('#Logs .userlogs li').removeClass('hover');\\n\" +\n            \"}\\n\" +\n            \"function reply_rtweet(logid, rid, toname){\\n\" +\n            \"\\tvar edtPost = $('#edt_tweet_post_' + logid);\\n\" +\n            \"\\tvar old_v = edtPost.val();\\n\" +\n            \"\\tif(old_v.length > 0)\\n\" +\n            \"\\t\\tedtPost.val(old_v + ',@'+toname+' ');\\n\" +\n            \"\\telse\\n\" +\n            \"\\t\\tedtPost.val('回复 @'+toname+' : ');\\n\" +\n            \"\\tedtPost.focus();\\n\" +\n            \"\\tedtPost.caretPos(edtPost.val().length); }\\n\" +\n            \"function delete_tweet(logid){\\n\" +\n            \"\\tif(confirm(\\\"确认要删除这条信息吗？\\\"))\\n\" +\n            \"\\tajax_post(\\\"/action/tweet/delete?log=\\\"+logid+\\\"&user=190591\\\",\\\"\\\",function(html){\\n\" +\n            \"\\t\\tif(html.length==0){\\n\" +\n            \"\\t\\t\\tvar elem = $('#LI_'+logid);\\n\" +\n            \"\\t\\t\\tif(elem.length > 0)\\n\" +\n            \"\\t\\t\\t\\t$('#LI_'+logid).fadeOut();\\n\" +\n            \"\\t\\t\\telse\\n\" +\n            \"\\t\\t\\t\\tlocation.reload();\\n\" +\n            \"\\t\\t}\\n\" +\n            \"\\t});\\n\" +\n            \"}\\n\" +\n            \"function delete_tweet_reply(logid){\\n\" +\n            \"\\tif(confirm(\\\"确认要删除这条评论吗？\\\"))\\n\" +\n            \"\\tajax_post(\\\"/action/tweet/delete_reply?id=\\\"+logid+\\\"&user=190591\\\",\\\"\\\",function(html){\\n\" +\n            \"\\t\\tif(html.length==0)\\n\" +\n            \"\\t\\t\\t$('#TweetReply_'+logid).fadeOut();\\n\" +\n            \"\\t});\\n\" +\n            \"}\\n\" +\n            \"\\n\" +\n            \"function delete_blog(blog_id){\\n\" +\n            \"    if(!confirm(\\\"文章删除后无法恢复，请确认是否删除此篇文章？\\\")) return;\\n\" +\n            \"    ajax_post(\\\"/action/blog/delete?id=\\\"+blog_id+\\\"&user=190591&user_code=tzm9Wg2YoU8SkJaTIjHQkahStiXQNyymUGXFOQgN\\\",\\\"\\\",function(html){\\n\" +\n            \"    \\tlocation.href=\\\"http://my.oschina.net/flashsword/blog\\\";\\n\" +\n            \"    });\\n\" +\n            \"}\\n\" +\n            \"\\n\" +\n            \"function SelectStyle(on,option){\\n\" +\n            \"\\tvar currentSort = $(on).attr('id');\\n\" +\n            \"\\tvar currentText = $(option+\\\" li.\\\"+currentSort+\\\" a\\\").html();\\n\" +\n            \"\\t$(on + \\\" .text\\\").html(currentText);\\n\" +\n            \"\\t$(on + \\\" .text\\\").hover(function(){\\n\" +\n            \"\\t\\t$(this).addClass(\\\"hover\\\")\\n\" +\n            \"\\t},function(){\\n\" +\n            \"\\t\\t$(this).removeClass(\\\"hover\\\")\\n\" +\n            \"\\t});\\n\" +\n            \"\\t$(option+\\\" li a\\\").each(function(index){\\n\" +\n            \"\\t\\t$(this).click(function(){\\n\" +\n            \"\\t\\t\\tthishtml = $(this).html();\\n\" +\n            \"\\t\\t\\t$(on + \\\" .text\\\").removeClass(\\\"on\\\").html(thishtml);\\t\\t\\n\" +\n            \"\\t\\t\\t$(\\\".selectbox select \\\").find(\\\"option\\\").removeAttr('selected').eq(index).attr(\\\"selected\\\",\\\"selected\\\");\\t\\n\" +\n            \"\\t\\t\\t$(option).hide()\\n\" +\n            \"\\t\\t\\treturn false;\\n\" +\n            \"\\t\\t});\\n\" +\n            \"\\t\\t\\n\" +\n            \"\\t});\\t\\t\\n\" +\n            \"\\t\\n\" +\n            \"\\t$(\\\".selectbox\\\").click(function(){\\t\\t\\n\" +\n            \"\\t\\t$(option).toggle();\\n\" +\n            \"\\t\\t$(on + \\\" .text\\\").toggleClass(\\\"on\\\");\\t\\t\\n\" +\n            \"\\t\\treturn false;\\n\" +\n            \"\\t});\\n\" +\n            \"\\t$(document).click(function(){\\n\" +\n            \"\\t\\t$(option).hide();\\t\\n\" +\n            \"\\t\\t$(on + \\\" .text\\\").removeClass(\\\"on\\\");\\n\" +\n            \"\\t});\\n\" +\n            \"\\t$(document).trigger('click');\\n\" +\n            \"\\n\" +\n            \"}\\n\" +\n            \"\\n\" +\n            \"//-->\\n\" +\n            \"</script>\\n\" +\n            \"</html>\\n\" +\n            \"\\n\" +\n            \"<!-- Generated by OsChina.NET (init:0[ms],page:83[ms],ip:58.241.37.50) -->\";\n}\n"
  },
  {
    "path": "webmagic-samples/src/test/java/us/codecraft/webmagic/processor/SinablogProcessorTest.java",
    "content": "package us.codecraft.webmagic.processor;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.pipeline.FilePipeline;\nimport us.codecraft.webmagic.pipeline.JsonFilePipeline;\nimport us.codecraft.webmagic.samples.SinaBlogProcessor;\nimport us.codecraft.webmagic.scheduler.FileCacheQueueScheduler;\n\nimport java.io.IOException;\n\n/**\n * @author code4crafter@gmail.com <br>\n *         Date: 13-6-9\n *         Time: 上午8:02\n */\npublic class SinablogProcessorTest {\n\n    @Ignore\n    @Test\n    public void test() throws IOException {\n        SinaBlogProcessor sinaBlogProcessor = new SinaBlogProcessor();\n        //pipeline是抓取结束后的处理\n        //默认放到/data/webmagic/ftl/[domain]目录下\n        JsonFilePipeline pipeline = new JsonFilePipeline(\"/data/webmagic/\");\n        //Spider.me()是简化写法，其实就是new一个啦\n        //Spider.pipeline()设定一个pipeline，支持链式调用\n        //ConsolePipeline输出结果到控制台\n        //FileCacheQueueSchedular保存url，支持断点续传，临时文件输出到/data/temp/webmagic/cache目录\n        //Spider.run()执行\n        Spider.create(sinaBlogProcessor).pipeline(new FilePipeline()).pipeline(pipeline).scheduler(new FileCacheQueueScheduler(\"/data/temp/webmagic/cache/\")).\n                run();\n    }\n}\n"
  },
  {
    "path": "webmagic-samples/src/test/java/us/codecraft/webmagic/samples/scheduler/DelayQueueSchedulerTest.java",
    "content": "package us.codecraft.webmagic.samples.scheduler;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Request;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author code4crafter@gmail.com\n */\npublic class DelayQueueSchedulerTest {\n\n    @Ignore(\"infinite\")\n    @Test\n    public void test() {\n        DelayQueueScheduler delayQueueScheduler = new DelayQueueScheduler(1, TimeUnit.SECONDS);\n        delayQueueScheduler.push(new Request(\"1\"), null);\n        while (true){\n            Request poll = delayQueueScheduler.poll(null);\n            System.out.println(System.currentTimeMillis()+\"\\t\"+poll);\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-saxon/README.md",
    "content": "webmagic-extension\n-------\nwebmagic的扩展模块，依赖Saxon进行xpath2.0解析支持。Saxon依赖包太大，不作为默认模块引入。"
  },
  {
    "path": "webmagic-saxon/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n    xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://maven.apache.org/POM/4.0.0\n        http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>us.codecraft</groupId>\n        <artifactId>webmagic</artifactId>\n        <version>1.0.4-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>webmagic-saxon</artifactId>\n\n    <properties>\n        <maven.deploy.skip>true</maven.deploy.skip>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-core</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>net.sourceforge.htmlcleaner</groupId>\n            <artifactId>htmlcleaner</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>net.sf.saxon</groupId>\n            <artifactId>Saxon-HE</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "webmagic-saxon/src/main/java/us/codecraft/webmagic/selector/JaxpSelectorUtils.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\n\nimport javax.xml.transform.OutputKeys;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerException;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\nimport java.io.StringWriter;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * @author hooy\n */\npublic final class JaxpSelectorUtils {\n\n    private JaxpSelectorUtils() {\n        throw new RuntimeException(\"The util class cannot be instanced\");\n    }\n\n    public static List<Node> NodeListToArrayList(NodeList nodes) {\n        List<Node> list = new ArrayList<>(nodes.getLength());\n        for (int i = 0; i < nodes.getLength(); i++) {\n            list.add(nodes.item(i));\n        }\n        return list;\n    }\n\n    public static String nodeToString(Node node) throws TransformerException {\n        List<Node> before = Collections.singletonList(node);\n        List<String> after = nodesToStrings(before);\n        if (after.size() > 0) {\n            return after.get(0);\n        } else {\n            return null;\n        }\n    }\n\n    public static List<String> nodesToStrings(List<Node> nodes) throws TransformerException {\n        List<String> results = new ArrayList<>(nodes.size());\n        Transformer transformer = TransformerFactory.newInstance().newTransformer();\n        StreamResult xmlOutput = new StreamResult();\n        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, \"yes\");\n        for (Node node : nodes) {\n            if (node.getNodeType() == Node.ATTRIBUTE_NODE || node.getNodeType() == Node.TEXT_NODE) {\n                results.add(node.getTextContent());\n            } else {\n                xmlOutput.setWriter(new StringWriter());\n                transformer.transform(new DOMSource(node), xmlOutput);\n                results.add(xmlOutput.getWriter().toString());\n            }\n        }\n        return results;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-saxon/src/main/java/us/codecraft/webmagic/selector/NodeSelector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport org.w3c.dom.Node;\n\nimport java.util.List;\n\n/**\n * Selector(extractor) for html node.<br>\n *\n * @author hooy <br>\n * @since 0.8.0\n */\npublic interface NodeSelector {\n\n    /**\n     * Extract single result in text.<br>\n     * If there are more than one result, only the first will be chosen.\n     *\n     * @param node node\n     * @return result\n     */\n    String select(Node node);\n\n    /**\n     * Extract all results in text.<br>\n     *\n     * @param node node\n     * @return results\n     */\n    List<String> selectList(Node node);\n\n}\n"
  },
  {
    "path": "webmagic-saxon/src/main/java/us/codecraft/webmagic/selector/Xpath2Selector.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport javax.xml.namespace.NamespaceContext;\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.xpath.XPathConstants;\nimport javax.xml.xpath.XPathExpression;\nimport javax.xml.xpath.XPathExpressionException;\n\nimport org.htmlcleaner.CleanerProperties;\nimport org.htmlcleaner.DomSerializer;\nimport org.htmlcleaner.HtmlCleaner;\nimport org.htmlcleaner.TagNode;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\n\nimport net.sf.saxon.lib.NamespaceConstant;\nimport net.sf.saxon.xpath.XPathEvaluator;\nimport us.codecraft.webmagic.utils.BaseSelectorUtils;\n\nimport static us.codecraft.webmagic.selector.JaxpSelectorUtils.*;\n\n/**\n * 支持xpath2.0的选择器。包装了HtmlCleaner和Saxon HE。<br>\n *\n * @author code4crafter@gmail.com, hooy <br>\n * Date: 13-4-21\n * Time: 上午9:39\n */\npublic class Xpath2Selector implements Selector, NodeSelector {\n\n    private final String xpathStr;\n\n    private XPathExpression xPathExpression;\n\n    private final Logger logger = LoggerFactory.getLogger(getClass());\n\n    public Xpath2Selector(String xpathStr) {\n        this.xpathStr = xpathStr;\n        try {\n            init();\n        } catch (XPathExpressionException e) {\n            throw new IllegalArgumentException(\"XPath error!\", e);\n        }\n    }\n\n    public static Xpath2Selector newInstance(String xpathStr) {\n        return new Xpath2Selector(xpathStr);\n    }\n\n    enum XPath2NamespaceContext implements NamespaceContext {\n\n        INSTANCE;\n\n        private final Map<String, String> prefix2NamespaceMap = new ConcurrentHashMap<>();\n\n        private final Map<String, List<String>> namespace2PrefixMap = new ConcurrentHashMap<>();\n\n        private void put(String prefix, String namespaceURI) {\n            prefix2NamespaceMap.put(prefix, namespaceURI);\n            List<String> prefixes = namespace2PrefixMap.computeIfAbsent(namespaceURI, k -> new ArrayList<>());\n            prefixes.add(prefix);\n        }\n\n        XPath2NamespaceContext() {\n            put(\"fn\", NamespaceConstant.FN);\n            put(\"xslt\", NamespaceConstant.XSLT);\n            put(\"xhtml\", NamespaceConstant.XHTML);\n        }\n\n        @Override\n        public String getNamespaceURI(String prefix) {\n            return prefix2NamespaceMap.get(prefix);\n        }\n\n        @Override\n        public String getPrefix(String namespaceURI) {\n            List<String> prefixes = namespace2PrefixMap.get(namespaceURI);\n            if (prefixes == null || prefixes.size() < 1) {\n                return null;\n            }\n            return prefixes.get(0);\n        }\n\n        @Override\n        public Iterator getPrefixes(String namespaceURI) {\n            List<String> prefixes = namespace2PrefixMap.get(namespaceURI);\n            if (prefixes == null || prefixes.size() < 1) {\n                return null;\n            }\n            return prefixes.iterator();\n        }\n    }\n\n    private void init() throws XPathExpressionException {\n        XPathEvaluator xPathEvaluator = new XPathEvaluator();\n        xPathEvaluator.setNamespaceContext(XPath2NamespaceContext.INSTANCE);\n        xPathExpression = xPathEvaluator.compile(xpathStr);\n    }\n\n    @Override\n    public String select(String text) {\n        try {\n            Document doc = parse(text);\n            return select(doc);\n        } catch (Exception e) {\n            logger.error(\"select text error! \" + xpathStr, e);\n        }\n        return null;\n    }\n\n    @Override\n    public String select(Node node) {\n        try {\n            return (String) xPathExpression.evaluate(node, XPathConstants.STRING);\n        } catch (Exception e) {\n            logger.error(\"select text error! \" + xpathStr, e);\n        }\n        return null;\n    }\n\n    @Override\n    public List<String> selectList(String text) {\n        try {\n            Document doc = parse(text);\n            return selectList(doc);\n        } catch (Exception e) {\n            logger.error(\"select text error! \" + xpathStr, e);\n        }\n        return null;\n    }\n\n    @Override\n    public List<String> selectList(Node node) {\n        try {\n            NodeList result = (NodeList) xPathExpression.evaluate(node, XPathConstants.NODESET);\n            List<Node> nodes = NodeListToArrayList(result);\n            return nodesToStrings(nodes);\n        } catch (Exception e) {\n            logger.error(\"select text error! \" + xpathStr, e);\n        }\n        return null;\n    }\n\n    public Node selectNode(String text) {\n        try {\n            Document doc = parse(text);\n            return selectNode(doc);\n        } catch (Exception e) {\n            logger.error(\"select text error! \" + xpathStr, e);\n        }\n        return null;\n    }\n\n    public Node selectNode(Node node) {\n        try {\n            return (Node) xPathExpression.evaluate(node, XPathConstants.NODE);\n        } catch (Exception e) {\n            logger.error(\"select text error! \" + xpathStr, e);\n        }\n        return null;\n    }\n\n    public List<Node> selectNodes(String text) {\n        try {\n            Document doc = parse(text);\n            return selectNodes(doc);\n        } catch (Exception e) {\n            logger.error(\"select text error! \" + xpathStr, e);\n        }\n        return null;\n    }\n\n    public List<Node> selectNodes(Node node) {\n        try {\n            NodeList result = (NodeList) xPathExpression.evaluate(node, XPathConstants.NODESET);\n            return NodeListToArrayList(result);\n        } catch (Exception e) {\n            logger.error(\"select text error! \" + xpathStr, e);\n        }\n        return null;\n    }\n\n    protected static Document parse(String text) throws ParserConfigurationException {\n        // HtmlCleaner could not parse <tr></tr> or <td></td> tag directly\n        text = BaseSelectorUtils.preParse(text);\n        HtmlCleaner htmlCleaner = new HtmlCleaner();\n        TagNode tagNode = htmlCleaner.clean(text);\n        return new DomSerializer(new CleanerProperties()).createDOM(tagNode);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-saxon/src/test/java/us/codecraft/webmagic/selector/XpathSelectorTest.java",
    "content": "package us.codecraft.webmagic.selector;\n\nimport java.util.List;\n\nimport org.htmlcleaner.HtmlCleaner;\nimport org.htmlcleaner.TagNode;\nimport org.htmlcleaner.XPatherException;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.junit.Assert;\nimport org.junit.Ignore;\nimport org.junit.Test;\n\nimport org.w3c.dom.Node;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.xsoup.XPathEvaluator;\nimport us.codecraft.xsoup.Xsoup;\n\nimport javax.xml.transform.TransformerException;\n\n/**\n * @author code4crafter@gmail.com <br> Date: 13-4-21 Time: 上午10:06\n */\npublic class XpathSelectorTest {\n\n    private String html = \"\\n\"\n            + \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\\n\"\n            + \"<html lang='zh-CN' xml:lang='zh-CN' xmlns='http://www.w3.org/1999/xhtml'>\\n\"\n            + \"<head>\\n\"\n            + \"  <meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"/>\\n\"\n            + \"  <link rel=\\\"shortcut icon\\\" type=\\\"image/x-icon\\\" href=\\\"/img/favicon.ico\\\" />\\n\"\n            + \"  <title>再次吐槽easyui - 开源中国 OSChina.NET</title>\\n\"\n            + \"      <link rel=\\\"stylesheet\\\" href=\\\"/css/style2013.css?ver=20130411\\\" type=\\\"text/css\\\" media=\\\"screen\\\" />\\n\"\n            + \"  <link rel=\\\"stylesheet\\\" href=\\\"/css/channel.css?date=20130324_2\\\" type=\\\"text/css\\\" media=\\\"screen\\\" />\\n\"\n            + \"  <link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" href=\\\"/js/2011/fancybox/jquery.fancybox-1.3.4.css\\\" media=\\\"screen\\\" />\\n\"\n            + \"  <link rel=\\\"alternate\\\" type=\\\"application/rss+xml\\\" title=\\\"最新代码分享列表\\\" href=\\\"http://www.oschina.net/code/rss\\\" />\\n\"\n            + \"  <link rel=\\\"alternate\\\" type=\\\"application/rss+xml\\\" title=\\\"开源中国 - 源码列表\\\" href=\\\"http://www.oschina.net/code/source_rss\\\" />\\n\"\n            + \"  <link rel=\\\"alternate\\\" type=\\\"application/rss+xml\\\" title=\\\"最新问题列表\\\" href=\\\"http://www.oschina.net/question/rss\\\" />\\n\"\n            + \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2012/jquery-1.7.1.min.js\\\"></script>\\n\"\n            + \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2012/jquery.form.js\\\"></script>\\n\"\n            + \"  <script type=\\\"text/javascript\\\" src=\\\"/js/2011/fancybox/jquery.fancybox-fixed.js?20130327\\\"></script>\\n\"\n            + \"      <link rel=\\\"stylesheet\\\" href=\\\"/js/poshytip/tip-yellowsimple/tip-yellowsimple.css\\\" type=\\\"text/css\\\" />\\n\"\n            + \"  <script type=\\\"text/javascript\\\" src=\\\"/js/poshytip/jquery.poshytip.min.js\\\"></script>\\n\"\n            + \"    <script type=\\\"text/javascript\\\">\\n\"\n            + \"  \\tg_msg = {\\n\"\n            + \"};\\n\"\n            + \"\\n\"\n            + \"g_user = {\\n\"\n            + \"\\tid:190591,\\n\"\n            + \"\\tname:'黄亿华',\\n\"\n            + \"\\tlogin:true,\\n\"\n            + \"\\tbportrait:'<img src=\\\"http://static.oschina.net/uploads/user/95/190591_50.jpg?t=1347254905000\\\" align=\\\"absmiddle\\\" alt=\\\"黄亿华\\\" title=\\\"黄亿华\\\" class=\\\"SmallPortrait\\\" user=\\\"190591\\\"/>'\\n\"\n            + \"};  </script>\\n\"\n            + \"    <script type=\\\"text/javascript\\\" src=\\\"/js/2011/oschina.js?ver=20121007\\\"></script>\\n\"\n            + \"  <script type=\\\"text/javascript\\\" src=\\\"/js/utils.js\\\"></script>\\n\"\n            + \"  <script type=\\\"text/javascript\\\" src=\\\"/js/channel.js\\\"></script>\\n\"\n            + \"      <style type=\\\"text/css\\\">\\n\"\n            + \"    body,table,input,textarea,select {font-family:Verdana,sans-serif,宋体;}  \\n\"\n            + \"  </style>\\n\"\n            + \"  </head>\\n\"\n            + \"<body>\\n\"\n            + \"<div id='OSC_NavTop'>\\n\"\n            + \"\\t<div class=\\\"wp998\\\">\\n\"\n            + \"        <div id=\\\"OSC_Channels\\\">\\n\"\n            + \"        \\t<ul>\\n\"\n            + \"        \\t<li class=\\\"item\\\"><a href=\\\"http://www.oschina.net/\\\" class='home'>首页</a></li>        \\t<li class=\\\"item\\\"><a href=\\\"http://www.oschina.net/project\\\" class='project'>开源软件</a></li>\\n\"\n            + \"        \\t<li class=\\\"item control_select\\\">\\n\"\n            + \"\\t\\t\\t\\t<a href=\\\"http://www.oschina.net/question\\\" class='question hl'>讨论区</a>\\t\\t\\t\\t\\n\"\n            + \"\\t\\t\\t\\t<ul class=\\\"cs_content\\\">\\t\\t\\t\\t\\t\\n\"\n            + \"                \\t<li><a href=\\\"http://www.oschina.net/question?catalog=1\\\"> 技术问答 &raquo; </a></li>\\n\"\n            + \"                \\t<li><a href=\\\"http://www.oschina.net/question?catalog=2\\\"> 技术分享 &raquo; </a></li>\\n\"\n            + \"                \\t<li><a href=\\\"http://www.oschina.net/question?catalog=3\\\"> IT大杂烩 &raquo; </a></li>\\n\"\n            + \"                \\t<li><a href=\\\"http://www.oschina.net/question?catalog=100\\\"> 职业生涯 &raquo; </a></li>\\n\"\n            + \"                \\t<li><a href=\\\"http://www.oschina.net/question?catalog=4\\\"> 站务/建议 &raquo; </a></li>\\n\"\n            + \"                \\t<li><a href=\\\"http://www.oschina.net/alipay\\\"> 支付宝专区 &raquo; </a></li>\\n\"\n            + \"\\t\\t\\t\\t</ul>\\n\"\n            + \"\\t\\t\\t</li>\\n\"\n            + \"        \\t<li class=\\\"item\\\"><a href=\\\"http://www.oschina.net/code/list\\\" class='code'>代码分享</a></li>\\n\"\n            + \"        \\t        \\t<li class=\\\"item\\\"><a href=\\\"http://www.oschina.net/blog\\\" class='blog'>博客</a></li>\\n\"\n            + \"        \\t<li class=\\\"item\\\"><a href=\\\"http://www.oschina.net/translate\\\" class='tran'>翻译</a></li>\\n\"\n            + \"            <li class=\\\"item\\\"><a href=\\\"http://www.oschina.net/news\\\" class='news'>资讯</a></li>\\n\"\n            + \"        \\t<li class=\\\"item control_select\\\">\\n\"\n            + \"\\t\\t\\t\\t<a href=\\\"http://www.oschina.net/android\\\" class='mobile'>移动开发</a>\\n\"\n            + \"\\t\\t\\t\\t<ul class=\\\"cs_content cs_mobile\\\">\\n\"\n            + \"                \\t<li class=\\\"android_\\\"><a href=\\\"http://www.oschina.net/android\\\">Android开发专区</a></li>\\n\"\n            + \"                \\t<li class=\\\"ios_\\\"><a href=\\\"http://www.oschina.net/ios/home\\\">iOS开发专区</a></li>\\n\"\n            + \"                \\t<li class=\\\"ios_\\\"><a href=\\\"http://www.oschina.net/ios/codingList\\\">iOS代码库</a></li>\\n\"\n            + \"                \\t<li class=\\\"wp7_\\\"><a href=\\\"http://www.oschina.net/wp7\\\">WP7开发专区</a></li>\\n\"\n            + \"\\t\\t\\t\\t</ul>\\n\"\n            + \"\\t\\t\\t</li>\\n\"\n            + \"        \\t<li class=\\\"item t_job\\\"><a href=\\\"http://www.oschina.net/job\\\" class='job'>招聘</a></li>\\n\"\n            + \"        \\t</ul>\\n\"\n            + \"        </div>\\n\"\n            + \"\\t\\t<div id=\\\"OSC_Userbar\\\">\\n\"\n            + \"                \\t\\t    \\t\\t<em>黄亿华</em>,您好 \\n\"\n            + \"\\t\\t\\t<span class=\\\"control_select\\\">\\n\"\n            + \"\\t\\t\\t\\t<a href=\\\"http://my.oschina.net/flashsword\\\" id=\\\"MySpace\\\" title=\\\"我的空间\\\">我的空间</a>\\n\"\n            + \"\\t\\t\\t\\t<ul class=\\\"cs_content cs_myspace\\\">\\n\"\n            + \"                \\t<li class='msg_'><a href='http://www.oschina.net/home/go?page=admin%2Finbox'>站内留言</a></li>\\n\"\n            + \"                \\t<li class='discuss_'><a href='http://my.oschina.net/flashsword/?ft=bbs&scope=2&showme=1'>我的讨论记录</a></li>\\n\"\n            + \"                \\t<li class='code_'><a href='http://www.oschina.net/code/list_by_user?id=190591'>我分享的代码</a></li>\\n\"\n            + \"                \\t<li class='blog_'><a href='http://www.oschina.net/home/go?page=blog'>我的博客</a></li>\\n\"\n            + \"                \\t<li class='friends_'><a href='http://www.oschina.net/home/go?page=fellow'>我关注的人</a></li>\\n\"\n            + \"                \\t<li class='favorites_'><a href='http://www.oschina.net/home/go?page=favorites'>我的收藏夹</a></li>\\n\"\n            + \"                \\t<li class='profile_'><a href='http://www.oschina.net/home/go?page=admin%2Fprofile'>个人资料修改</a></li>\\n\"\n            + \"                \\t<li class='portrait_'><a href='http://www.oschina.net/home/go?page=admin%2Fportrait'>更改我的头像</a></li>\\n\"\n            + \"\\t\\t\\t\\t</ul>\\n\"\n            + \"\\t\\t\\t</span>&nbsp;|&nbsp;\\n\"\n            + \"\\t\\t\\t<a href=\\\"http://www.oschina.net/home/go?page=admin%2Fnew-project\\\">添加软件</a>&nbsp;|&nbsp;<a href=\\\"http://www.oschina.net/home/go?page=admin%2Fnew-release\\\">投递新闻</a>&nbsp;|&nbsp;<a href=\\\"/action/user/logout?session=6db40e6e2d1061998068&goto_page=http%3A%2F%2Fwww.oschina.net%2Fquestion%2F818848_107307\\\">退出</a>\\n\"\n            + \"    \\t\\t\\t\\t</div>\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t</div>\\n\"\n            + \"</div>\\n\"\n            + \"<div id='OSC_Banner'><div class=\\\"wp998\\\"><a href='http://www.oschina.net/' class='Logo' title='OSChina 开源中国'>开源中国</a>\\n\"\n            + \"<h1><a href='/question'>讨论区</a></h1>\\n\"\n            + \"<dl>\\n\"\n            + \"\\t<dt>当前位置：</dt>\\n\"\n            + \"\\t<dd>\\n\"\n            + \"\\t\\t\\t\\t\\t        \\t\\t<a href=\\\"/question\\\">讨论区</a>&nbsp;&raquo;\\n\"\n            + \"        \\t\\t<a href=\\\"/question?catalog=1\\\">技术问答</a>\\t\\t\\t\\t\\t\\t\\t\\t&raquo;&nbsp;<a href=\\\"/p/jquery+easyui\\\">EasyUI</a>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t</dd>\\n\"\n            + \"</dl>\\n\"\n            + \"<form action='http://www.oschina.net/search' class='search'>\\n\"\n            + \"\\t<input type='hidden' name='scope' value='bbs'/>\\n\"\n            + \"\\t<input id='channel_q' type='text' name='q' value='' placeholder='资讯、软件、分享、代码、博客' class='TXT'/>\\n\"\n            + \"    <button type='submit' class='BTN'>搜 索</button>\\n\"\n            + \"</form>\\n\"\n            + \"<div class='clear'></div></div></div>\\n\"\n            + \"<div id=\\\"OSC_Screen\\\">\\n\"\n            + \"\\t<div id=\\\"OSC_Content\\\" class='CenterDiv'>\\n\"\n            + \"<script type=\\\"text/javascript\\\" src=\\\"/js/scrolltopcontrol.js\\\"></script>\\n\"\n            + \"<script type=\\\"text/javascript\\\">\\n\"\n            + \"  \\tscrolltotop.offset(100,120);\\n\"\n            + \"  \\tscrolltotop.init();\\n\"\n            + \"\\t$(function(){\\n\"\n            + \"\\t\\t$('a.ShowUserOutline img.SmallPortrait').poshytip({\\n\"\n            + \"\\t\\t\\tclassName: 'tip-yellowsimple',\\n\"\n            + \"\\t\\t\\talignTo: 'target',\\n\"\n            + \"\\t\\t\\talignX: 'right',\\n\"\n            + \"\\t\\t\\talignY: 'inner-top',\\n\"\n            + \"\\t\\t\\toffsetX: 5,\\n\"\n            + \"\\t\\t\\toffsetY: 0,\\n\"\n            + \"\\t\\t\\tfade: false,\\n\"\n            + \"\\t\\t\\tslide: false,\\n\"\n            + \"\\t\\t\\tcontent: function(updateCallback) {\\n\"\n            + \"\\t\\t\\t\\tajax_get(\\\"/action/ajax/get_user_outline?id=\\\"+$(this).attr('user'),false,function(html){\\n\"\n            + \"\\t\\t\\t\\t\\tupdateCallback(html);\\n\"\n            + \"\\t\\t\\t\\t});\\n\"\n            + \"\\t\\t\\t\\treturn \\\"<div style='height:100px;'>Loading...</div>\\\";\\n\"\n            + \"\\t\\t\\t}\\n\"\n            + \"\\t\\t});\\n\"\n            + \"\\t});\\n\"\n            + \"\\tfunction add_to_favorite(pid,concern_it){\\n\"\n            + \"\\t\\t\\tif(concern_it){\\n\"\n            + \"\\t\\t\\t$(\\\"#p_attention_count\\\").load(\\\"/action/favorite/add?mailnotify=true&type=2&id=\\\"+pid, {user: '190591'});\\n\"\n            + \"\\t\\t\\t$('#attention_it').html('<a href=\\\"javascript:add_to_favorite('+pid+',false)\\\">取消</a>');\\t\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t\\telse{\\n\"\n            + \"\\t\\t\\t$(\\\"#p_attention_count\\\").load(\\\"/action/favorite/cancel?type=2&id=\\\"+pid, {user: '190591'});\\n\"\n            + \"\\t\\t\\t$('#attention_it').html('<a href=\\\"javascript:add_to_favorite('+pid+',true)\\\">收藏</a>');\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t\\t}\\n\"\n            + \"</script>\\n\"\n            + \"\\n\"\n            + \"\\n\"\n            + \"\\n\"\n            + \"<div class='Question'>\\n\"\n            + \"\\t\\n\"\n            + \"\\t<div class='Body'>\\n\"\n            + \"\\t<div class='Title'>\\n\"\n            + \"\\t\\t<div class='Asker'><a href=\\\"http://my.oschina.net/u/818848\\\" class=\\\"ShowUserOutline\\\" target=\\\"_blank\\\"><img src=\\\"http://static.oschina.net/uploads/user/409/818848_50.jpg?t=1357353541000\\\" align=\\\"absmiddle\\\" alt=\\\"午后冬日\\\" title=\\\"午后冬日\\\" class=\\\"SmallPortrait\\\" user=\\\"818848\\\"/></a></div>\\n\"\n            + \"\\t\\t<div class='QTitle'>\\n\"\n            + \"\\t\\t\\t<h1><a href=\\\"http://www.oschina.net/question/818848_107307\\\" hidefocus=\\\"true\\\" name='top'>再次吐槽easyui</a></h1>\\n\"\n            + \"\\t\\t\\t<div class='stat'>\\n\"\n            + \"\\t\\t\\t\\t<a href=\\\"http://my.oschina.net/u/818848\\\" target=\\\"_blank\\\">午后冬日</a>\\n\"\n            + \"\\t\\t\\t\\t发表于 2013-4-21 02:28 13小时前,\\n\"\n            + \"\\t\\t\\t\\t<a href='#answers' class='answer_count'>3</a>回/289阅,\\n\"\n            + \"\\t\\t\\t\\t最后回答: 4小时前\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t</div>\\n\"\n            + \"\\t\\t</div>\\n\"\n            + \"\\t\\t\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t</div>\\n\"\n            + \"\\t\\t    \\t    \\t\\t\\t\\t\\t\\n\"\n            + \"\\t\\t<p style='color:#A00;font-weight:bold;margin:10px 0 0 3px;'>Java、PHP、Ruby、iOS、Python 等 JetBrains 开发工具低至 99  元（3折），<a href='http://www.oschina.net/shop/jetbrains' target='_blank'>详情&raquo;</a></p>\\n\"\n            + \"\\t\\t<div class='Content'>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t<div class='detail'>刚用到easyui treegrid组件，发现这货第一次加载时候并没有传默认参数，展开某一列时候才传递id:xx的参数。这样和后台总是疙里疙瘩，像没事就拌嘴的两口子，查网上都遇到相同问题，最好解决方案就是通过 \\n\"\n            + \"<span style=\\\"font-family:Arial, Helvetica, 'Nimbus Sans L', sans-serif;font-size:14px;line-height:normal;background-color:#FFFFFF;\\\">onBeforeExpand事件来扩展，自行解决。看到官方例子中简洁的代码，感觉easyui耍流氓了，真搞不懂为何要这样实现</span><div class='clear'></div></div>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t<div class='Tags'>\\n\"\n            + \"\\t\\t\\t\\t<strong>标签：</strong>\\t\\t\\t\\t\\n\"\n            + \"\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t<a href='http://www.oschina.net/question/tag/jquery+easyui' class='tag project' title='jQuery的UI组件 EasyUI'>EasyUI</a> \\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t</div>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t<div class='SameQuestions'>\\n\"\n            + \"\\t\\t\\t<span id='RQuestionAction'>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t\\t\\t\\t<a href=\\\"javascript:ask_too(107307,true)\\\" class='rndbutton'><span>我想问同样的问题</span></a>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t</span>\\n\"\n            + \"\\t\\t\\t共<em id='c_asker_count'>0</em>个人想要问同样的问题\\n\"\n            + \"\\t\\t\\t\\t\\t\\t<a href=\\\"javascript:make_question_more_detail(107307)\\\">补充话题说明&raquo;</a>\\n\"\n            + \"\\t\\t\\t</div>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t<div class='EditLogs'>\\n\"\n            + \"\\t<ul></ul>\\n\"\n            + \"</div>\\t\\t</div>\\n\"\n            + \"\\t\\t<style type='text/css'>\\n\"\n            + \"\\t\\t#favor_form{width:200px;}\\n\"\n            + \"        #favor_form p {color:#666;}\\n\"\n            + \"        #favor_form form{height:60px;width:200px;}\\n\"\n            + \"        #favor_form form ._favor_input{display:block;margin:2px 0;width:199px;}\\n\"\n            + \"        #favor_form form ._favor_button{float:left;padding:2px 5px;}\\n\"\n            + \"        .favor_ok {text-align:center;font-size:10.5pt;width:199px;height:60px;margin-top:10px;}\\n\"\n            + \"        #TagsSwitcher{cursor:pointer;float:right;margin-top:10px;}\\n\"\n            + \"        #MyTags{display:none;width:199px;}\\n\"\n            + \"        #MyTags a.tag {float:left; background-color: #E0EAF1;border-bottom: 1px solid #3E6D8E;border-right: 1px solid #7F9FB6;color: #3E6D8E;font-size: 8pt;line-height: 16px;margin: 2px 2px 2px 0;padding: 2px 4px;text-decoration: none;white-space: nowrap;}\\n\"\n            + \"\\t\\t.osc_promotion{ position: relative; display: inline-block; padding: 10px; margin: 10px 0; border: 1px solid #ccc;}\\n\"\n            + \"        .osc_promotion .c{ position: absolute; right: -1px; top: -1px;}\\n\"\n            + \"\\t\\t.ask_toolbar {float:right;list-style: none; font-size: 12px; color: #333; height: 28px;_padding-top: 5px; overflow: hidden;margin:20px 0 10px 0;}\\n\"\n            + \"        .ask_toolbar div{ float: left; margin-left: 5px; background: url(\\\"/img/ask-icon.gif\\\") no-repeat; padding: 6px 0 6px 15px; padding-left: 15px; height: 16px;}\\n\"\n            + \"        .ask_toolbar a{ height: 16px; color: #333; text-decoration: none; display:inline-block; zoom:1; vertical-align: middle; }\\n\"\n            + \"        .ask_share{width: 89px;vertical-align: bottom; line-height: 15px; _line-height: 14px;}\\n\"\n            + \"        .ask_share a{background: url(\\\"/img/ask-icon.gif\\\"); width: 16px; }\\n\"\n            + \"        a.ask_share_sina{ background: url(\\\"/img/ask-icon.gif\\\") 0 -40px no-repeat; margin-left: 5px;  }\\n\"\n            + \"        a.ask_share_tencent{background-position: 0 -70px; margin-left: 5px; }\\n\"\n            + \"        .ask_toolbar em{ height: 28px; line-height:28px; width: 14px; display: inline-block; float: left; background: url(\\\"/img/ask-icon.gif\\\") top right;}\\n\"\n            + \"        .ask_collect a,.ask_report a, .ask_vote a, .ask_collected a{padding-left: 20px; line-height: 15px; }\\n\"\n            + \"        .ask_collect a{ background: url(\\\"/img/ask-icon.gif\\\") 0 -131px no-repeat; }\\n\"\n            + \"        .ask_collected a{ background: url(\\\"/img/ask-icon.gif\\\") 0 -100px no-repeat; }\\n\"\n            + \"        div.ask_collect_count{ background: url(\\\"/img/ask-icon.gif\\\") 0 -309px no-repeat; font-weight: bold; font-size: 14px; margin-left: 0; height: 16px;line-height: 16px;}\\n\"\n            + \"        .ask_report a{ background: url(\\\"/img/ask-icon.gif\\\") 0 -160px no-repeat;}\\n\"\n            + \"        em.ask_collect_count_r{background-position: 59px -309px;}\\n\"\n            + \"        .ask_vote a{background: url(\\\"/img/ask-icon.gif\\\");}\\n\"\n            + \"        \\t\\ta.ask_vote_up{background-position: 3px -190px;}\\n\"\n            + \"        a.ask_vote_down{background-position: 0 -280px;}\\n\"\n            + \"        a.ask_vote_uped {background-position: 3px -190px;}\\n\"\n            + \"        \\t\\ta.ask_vote_downed {background-position: 0 -280px;}\\n\"\n            + \"        .ask_vote span{ display: inline-block; margin: 0 10px; font-weight: bold; font-size: 14px; vertical-align: middle; margin-bottom: 2px; line-height: 16px;}\\n\"\n            + \"\\t\\tspan.vote-down-count{margin:0 3px;}\\n\"\n            + \"\\t\\tspan.vote-up-count{margin:0 3px;}\\n\"\n            + \"\\t\\t</style>\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"        \\n\"\n            + \"\\t\\t\\t\\t<div class=\\\"ask_toolbar\\\">\\n\"\n            + \"\\t\\t\\t<div class=\\\"ask_share\\\"><b>分享到</b> <a class=\\\"ask_share_sina\\\" title=\\\"分享到新浪微博\\\"  href=\\\"javascript:void((function(s,d,e,r,l,p,t,z,c){var%20f='http://v.t.sina.com.cn/share/share.php?appkey=858381728',u=z||d.location,p=['&url=',e(u),'&title=',e(t||d.title),'&source=',e(r),'&sourceUrl=',e(l),'&content=',c||'gb2312','&pic=',e(p||'')].join('');function%20a(){if(!window.open([f,p].join(''),'mb',['toolbar=0,status=0,resizable=1,width=440,height=430,left=',(s.width-440)/2,',top=',(s.height-430)/2].join('')))u.href=[f,p].join('');};if(/Firefox/.test(navigator.userAgent))setTimeout(a,0);else%20a();})(screen,document,encodeURIComponent,'','','','再次吐槽easyui: 刚用到easyui treegrid组件，发现这货第一次加载时候并没有传默认参数，展开某一列时候才传递id:xx的参数。这样和后台总是疙里疙瘩，像没事就拌嘴的两口子，查网上都遇到...','','utf-8'));\\\"></a><a class=\\\"ask_share_tencent\\\" title=\\\"分享到腾讯微博\\\"  href=\\\"javascript:(function(){window.open('http://v.t.qq.com/share/share.php?url='+encodeURIComponent(document.location)+'&amp;appkey=96f54f97c4de46e393c4835a266207f4&amp;site=&amp;title='+encodeURIComponent(document.title)+encodeURIComponent(': 刚用到easyui treegrid组件，发现这货第一次加载时候并没有传默认参数，展开某一列时候才传递id:xx的参数。这样和后台总是疙里疙瘩，像没事就拌嘴的两口子，查网上都遇到...'),'', 'width=450, height=400, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=yes, resizable=no, status=no');}())\\\"></a></div><em></em>\\n\"\n            + \"\\t\\t\\t<div class=\\\"ask_collect\\\"><a title=\\\"收藏此话题\\\" id=\\\"favor_trigger\\\" href=\\\"javascript:;\\\">收藏</a></div><em></em>\\n\"\n            + \"\\t\\t\\t<div class=\\\"ask_collect_count\\\" id=\\\"p_favor_count\\\">1</div><em class=\\\"ask_collect_count_r\\\"></em>\\n\"\n            + \"\\t\\t\\t<div class=\\\"ask_report\\\"><a href=\\\"javascript:report('http://www.oschina.net/question/818848_107307',107307,2)\\\">举报</a></div><em></em>\\n\"\n            + \"\\t\\t\\t<div class='ask_vote' id='Vote'>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t\\t\\t        \\t\\t\\t<a id=\\\"vote_down\\\" class=\\\"ask_vote_down\\\" href=\\\"javascript:;\\\" title=\\\"踩：这问题不知道在说什么，或者没什么用\\\">踩</a>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t\\t\\t<span class='vote-down-count'>0</span>\\n\"\n            + \"\\t\\t\\t\\t|\\n\"\n            + \"\\t\\t\\t\\t\\t\\t\\t\\t        \\t\\t\\t<a id=\\\"vote_up\\\" class=\\\"ask_vote_up\\\" href=\\\"javascript:;\\\" title=\\\"顶：这问题很有用或者很清晰明了\\\">顶</a>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t\\t\\t<span class='vote-up-count'>0</span>\\n\"\n            + \"\\t\\t\\t</div>\\n\"\n            + \"\\t\\t\\t<em></em>\\n\"\n            + \"\\t\\t</div>\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t        <div class='QuestionReplies'>\\n\"\n            + \"\\t\\t\\t\\n\"\n            + \"        \\t<h2>\\t\\t\\t\\n\"\n            + \"\\t\\t\\t\\t<span class='sort'>\\n\"\n            + \"\\t\\t\\t\\t\\t<a href=\\\"http://www.oschina.net/question/818848_107307#answers\\\" class='current'>按评价排序</a>&nbsp;|\\n\"\n            + \"\\t\\t\\t\\t\\t<a href=\\\"?sort=time#answers\\\">显示最新答案</a>&nbsp;|&nbsp;<a href=\\\"#top\\\" style='padding-left:0;'>回页面顶部</a>\\n\"\n            + \"\\t\\t\\t\\t</span>\\n\"\n            + \"\\t\\t\\t\\t<a name='answers'></a>共有<em class='answer_count'>3</em>个答案 <a href=\\\"#answerform\\\" class='answer'>我要回答&raquo;</a>\\n\"\n            + \"\\t\\t\\t</h2>\\n\"\n            + \"\\t\\t\\t        \\t<ul class='list'><li class='Answer' id='answer_467005'>\\n\"\n            + \"\\t<div class='user'><a href=\\\"http://my.oschina.net/u/224858\\\" class=\\\"ShowUserOutline\\\" name=\\\"AnchorAnswer467005\\\" target=\\\"_blank\\\"><img src=\\\"http://static.oschina.net/uploads/user/112/224858_50.jpg\\\" align=\\\"absmiddle\\\" alt=\\\"布谷鸟\\\" title=\\\"布谷鸟\\\" class=\\\"SmallPortrait\\\" user=\\\"224858\\\"/></a></div>\\n\"\n            + \"\\t<div class='body'>\\n\"\n            + \"\\t\\t<div class='time'><a href=\\\"http://my.oschina.net/u/224858\\\" target=\\\"_blank\\\">布谷鸟</a> 回答于 2013-04-21 09:28 </div>\\t\\t\\n\"\n            + \"    \\t<div class='opts'>\\n\"\n            + \"\\t\\t\\t    \\t\\t    \\t\\t<a href=\\\"javascript:report('http://www.oschina.net/question/818848_107307#AnchorAnswer467005',467005,17)\\\">举报</a>\\n\"\n            + \"    \\t</div>\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t\\t<div class='detail'>对话框、日期控件和选项卡效果还不错，树状菜单没得zTree好用，建议楼主不要全部效果都依赖于此框架，有些easyui实现不好的地方可以换其它的插件实现嘛，反正我现在再也不用诸如ext和easyui之类的东西了，感觉好肥</div>\\n\"\n            + \"\\t</div>\\n\"\n            + \"\\t<div class='clear'></div>\\n\"\n            + \"\\t<div class='replies' id='PostReplies_467005'><strong>--- 共有 1 条评论 --- </strong>\\n\"\n            + \"<ul>\\n\"\n            + \"\\t\\t<li id='PostReply_467044'>\\n\"\n            + \"\\t\\t<a href=\\\"http://my.oschina.net/u/818848\\\" class='p' name='AnchorAnswer467044'><img src=\\\"http://static.oschina.net/uploads/user/409/818848_50.jpg?t=1357353541000\\\" align=\\\"absmiddle\\\" alt=\\\"午后冬日\\\" title=\\\"午后冬日\\\" class=\\\"SmallPortrait\\\" user=\\\"818848\\\"/></a>\\n\"\n            + \"\\t\\t<span class='b'>\\n\"\n            + \"\\t\\t<span class='c'>前端水平实在有限，自己搞的总是感觉不伦不类，只能用这些框架，再集成其它插件，切换主题时风格又不一致。</span>\\n\"\n            + \"\\t\\t<span class='t'>(4小时前 by 午后冬日)</span>\\n\"\n            + \"\\t\\t<span class='opts'><a href=\\\"javascript:reply_to_post(467005,818848)\\\">回复</a></span>\\n\"\n            + \"\\t\\t</span>\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t</li>\\n\"\n            + \"\\t</ul>\\n\"\n            + \"<div class='PagerLinks'>\\n\"\n            + \"</div>\\n\"\n            + \"</div>\\n\"\n            + \"\\t<div class='votes'>\\t\\t\\t\\t\\t\\t<a id='a_post_voteup_467005' href=\\\"javascript:vote_answer(467005,true,true)\\\" title=\\\"这是一个好答案，能解决问题\\\">有帮助</a><em id='post_voteup_467005'>(1)</em>&nbsp;|\\n\"\n            + \"\\t\\t<a id='a_post_votedown_467005' href=\\\"javascript:vote_answer(467005,false,true)\\\" title=\\\"这答案无法解决问题，或者模糊不清\\\">没帮助</a><em id='post_votedown_467005'>(0)</em>&nbsp;|\\n\"\n            + \"\\t\\t<a href=\\\"javascript:reply_to_post(467005, 0)\\\">评论</a><em>(1)</em>&nbsp;|\\n\"\n            + \"    \\t<a href=\\\"/question/answer?question=107307&amp;answer=467005\\\">引用此答案</a>\\t</div>\\n\"\n            + \"</li><li class='Answer' id='answer_467039'>\\n\"\n            + \"\\t<div class='user'><a href=\\\"http://my.oschina.net/rox\\\" class=\\\"ShowUserOutline\\\" name=\\\"AnchorAnswer467039\\\" target=\\\"_blank\\\"><img src=\\\"http://static.oschina.net/uploads/user/0/180_50.jpg\\\" align=\\\"absmiddle\\\" alt=\\\"静风流云\\\" title=\\\"静风流云\\\" class=\\\"SmallPortrait\\\" user=\\\"180\\\"/></a></div>\\n\"\n            + \"\\t<div class='body'>\\n\"\n            + \"\\t\\t<div class='time'><a href=\\\"http://my.oschina.net/rox\\\" target=\\\"_blank\\\">静风流云</a> 回答于 2013-04-21 11:08 </div>\\t\\t\\n\"\n            + \"    \\t<div class='opts'>\\n\"\n            + \"\\t\\t\\t    \\t\\t    \\t\\t<a href=\\\"javascript:report('http://www.oschina.net/question/818848_107307#AnchorAnswer467039',467039,17)\\\">举报</a>\\n\"\n            + \"    \\t</div>\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t\\t<div class='detail'><p> 没办法，原来项目也是因为客户特殊的需求，对layout选型的时候，犹豫了好久，最终放弃了。<br /> 幸亏来了一个厉害的前端，解决问题，够用就好。 </p></div>\\n\"\n            + \"\\t</div>\\n\"\n            + \"\\t<div class='clear'></div>\\n\"\n            + \"\\t<div class='replies' id='PostReplies_467039'><strong>--- 共有 1 条评论 --- </strong>\\n\"\n            + \"<ul>\\n\"\n            + \"\\t\\t<li id='PostReply_467046'>\\n\"\n            + \"\\t\\t<a href=\\\"http://my.oschina.net/u/818848\\\" class='p' name='AnchorAnswer467046'><img src=\\\"http://static.oschina.net/uploads/user/409/818848_50.jpg?t=1357353541000\\\" align=\\\"absmiddle\\\" alt=\\\"午后冬日\\\" title=\\\"午后冬日\\\" class=\\\"SmallPortrait\\\" user=\\\"818848\\\"/></a>\\n\"\n            + \"\\t\\t<span class='b'>\\n\"\n            + \"\\t\\t<span class='c'>我也是犹豫了好久，看过很多前端框架，总是不太满意。个人开发前台后台数据库全部要自己搞定，郁闷ing</span>\\n\"\n            + \"\\t\\t<span class='t'>(4小时前 by 午后冬日)</span>\\n\"\n            + \"\\t\\t<span class='opts'><a href=\\\"javascript:reply_to_post(467039,818848)\\\">回复</a></span>\\n\"\n            + \"\\t\\t</span>\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t</li>\\n\"\n            + \"\\t</ul>\\n\"\n            + \"<div class='PagerLinks'>\\n\"\n            + \"</div>\\n\"\n            + \"</div>\\n\"\n            + \"\\t<div class='votes'>\\t\\t\\t\\t\\t\\t<a id='a_post_voteup_467039' href=\\\"javascript:vote_answer(467039,true,true)\\\" title=\\\"这是一个好答案，能解决问题\\\">有帮助</a><em id='post_voteup_467039'>(0)</em>&nbsp;|\\n\"\n            + \"\\t\\t<a id='a_post_votedown_467039' href=\\\"javascript:vote_answer(467039,false,true)\\\" title=\\\"这答案无法解决问题，或者模糊不清\\\">没帮助</a><em id='post_votedown_467039'>(0)</em>&nbsp;|\\n\"\n            + \"\\t\\t<a href=\\\"javascript:reply_to_post(467039, 0)\\\">评论</a><em>(1)</em>&nbsp;|\\n\"\n            + \"    \\t<a href=\\\"/question/answer?question=107307&amp;answer=467039\\\">引用此答案</a>\\t</div>\\n\"\n            + \"</li><li class='Answer' id='answer_467051'>\\n\"\n            + \"\\t<div class='user'><a href=\\\"http://my.oschina.net/u/224858\\\" class=\\\"ShowUserOutline\\\" name=\\\"AnchorAnswer467051\\\" target=\\\"_blank\\\"><img src=\\\"http://static.oschina.net/uploads/user/112/224858_50.jpg\\\" align=\\\"absmiddle\\\" alt=\\\"布谷鸟\\\" title=\\\"布谷鸟\\\" class=\\\"SmallPortrait\\\" user=\\\"224858\\\"/></a></div>\\n\"\n            + \"\\t<div class='body'>\\n\"\n            + \"\\t\\t<div class='time'><a href=\\\"http://my.oschina.net/u/224858\\\" target=\\\"_blank\\\">布谷鸟</a> 回答于 2013-04-21 11:29 </div>\\t\\t\\n\"\n            + \"    \\t<div class='opts'>\\n\"\n            + \"\\t\\t\\t    \\t\\t    \\t\\t<a href=\\\"javascript:report('http://www.oschina.net/question/818848_107307#AnchorAnswer467051',467051,17)\\\">举报</a>\\n\"\n            + \"    \\t</div>\\n\"\n            + \"\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t\\t<div class='detail'><div class=\\\"ref\\\"><h4>引用来自“布谷鸟”的答案</h4><div class=ref_body>对话框、日期控件和选项卡效果还不错，树状菜单没得zTree好用，建议楼主不要全部效果都依赖于此框架，有些easyui实现不好的地方可以换其它的插件实现嘛，反正我现在再也不用诸如ext和easyui之类的东西了，感觉好肥</div></div><div class=a_body>前后端你一个人搞啊？那确实很麻烦。面面俱到的话，工作量很大。但是如果需要实现的功能不是很多，而时间也不紧迫的话，事情干起来也还不错。如非必须，建议逐步弃用这些前端框架，在一些比较能够提升体验的地方选用一些适当的插件即可，如此也不再需要担心风格的问题，你看osc后台截图，界面那叫一个丑，用得方便顺手就够了</div></div>\\n\"\n            + \"\\t</div>\\n\"\n            + \"\\t<div class='clear'></div>\\n\"\n            + \"\\t<div class='replies' id='PostReplies_467051'></div>\\n\"\n            + \"\\t<div class='votes'>\\t\\t\\t\\t\\t\\t<a id='a_post_voteup_467051' href=\\\"javascript:vote_answer(467051,true,true)\\\" title=\\\"这是一个好答案，能解决问题\\\">有帮助</a><em id='post_voteup_467051'>(0)</em>&nbsp;|\\n\"\n            + \"\\t\\t<a id='a_post_votedown_467051' href=\\\"javascript:vote_answer(467051,false,true)\\\" title=\\\"这答案无法解决问题，或者模糊不清\\\">没帮助</a><em id='post_votedown_467051'>(0)</em>&nbsp;|\\n\"\n            + \"\\t\\t<a href=\\\"javascript:reply_to_post(467051, 0)\\\">评论</a><em>(0)</em>&nbsp;|\\n\"\n            + \"    \\t<a href=\\\"/question/answer?question=107307&amp;answer=467051\\\">引用此答案</a>\\t</div>\\n\"\n            + \"</li></ul>\\n\"\n            + \"\\t\\t\\t\\t        </div> \\n\"\n            + \"\\t\\t<div class='AnswerForm'>\\n\"\n            + \"\\t\\t\\t<div class='user'><a href=\\\"http://my.oschina.net/flashsword\\\" name=\\\"answerform\\\"><img src=\\\"http://static.oschina.net/uploads/user/95/190591_50.jpg?t=1347254905000\\\" align=\\\"absmiddle\\\" alt=\\\"黄亿华\\\" title=\\\"黄亿华\\\" class=\\\"SmallPortrait\\\" user=\\\"190591\\\"/></a></div>\\n\"\n            + \"\\t\\t\\t<form id='form_answer' action=\\\"/action/question/answer?question=107307\\\" method=\\\"post\\\">\\n\"\n            + \"\\t\\t\\t\\t<input type='hidden' name='user' value='190591'/>\\n\"\n            + \"\\t\\t\\t\\t<textarea id='txt_answner' name='body' style='width:560px;height:160px;'></textarea>\\n\"\n            + \"\\t\\t\\t\\t<input type='submit' value=' 我要回答 ' id=\\\"FormSubmitButton\\\" class='rndbutton'/>\\n\"\n            + \"\\t\\t\\t\\t<span id='form_msg' style='display:none;'></span>\\n\"\n            + \"\\t\\t\\t\\t<br><br>\\n\"\n            + \"\\t\\t\\t\\t<a href=\\\"#answers\\\">回答案顶部</a>&nbsp;|&nbsp;<a href=\\\"#top\\\">回页面顶部</a>\\n\"\n            + \"\\t\\t\\t</form>\\n\"\n            + \"\\t\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t\\t\\t<script>\\t\\t\\t\\n\"\n            + \"            $('#form_answer').ajaxForm({\\n\"\n            + \"            \\tdataType: 'json',\\n\"\n            + \"        \\t\\tbeforeSerialize: function($form, options) { \\n\"\n            + \"        \\t\\t\\teditor.sync();           \\n\"\n            + \"                },\\n\"\n            + \"        \\t\\tbeforeSubmit: function(){\\n\"\n            + \"        \\t\\t\\t$('#FormSubmitButton').attr('disabled','disabled');\\n\"\n            + \"        \\t\\t\\t$('#form_msg').html(\\\"<span class='ajax_processing'>正在提交答案，请稍候...</span>\\\");\\t\\n\"\n            + \"        \\t\\t\\t$('#form_msg').show();\\t\\n\"\n            + \"        \\t\\t},\\n\"\n            + \"                success: function(json) {\\n\"\n            + \"        \\t\\t\\t$('#FormSubmitButton').removeAttr('disabled');\\n\"\n            + \"            \\t\\tif(json.msg){\\n\"\n            + \"        \\t\\t\\t\\t$('#form_msg').html(\\\"<span class='error_msg'>\\\"+json.msg+\\\"</span>\\\");\\n\"\n            + \"        \\t\\t\\t\\t$('#form_msg').show();\\n\"\n            + \"        \\t\\t\\t}\\n\"\n            + \"            \\t\\telse if(json.id){\\n\"\n            + \"            \\t\\t\\tajax_get(\\\"/question/show_answer?_answer_id=\\\"+json.id, true, function(data){\\n\"\n            + \"        \\t\\t\\t\\t\\t            \\t\\t\\t\\t$('.QuestionReplies ul.list').append(data);\\n\"\n            + \"        \\t\\t\\t\\t\\t        \\t\\t\\t\\t\\teditor.html('');\\n\"\n            + \"        \\t\\t\\t\\t\\t$('.answer_count').html(json.answer_count);\\n\"\n            + \"            \\t\\t\\t}); \\t\\t\\t\\t\\n\"\n            + \"        \\t\\t\\t\\t$('#form_msg').hide();\\n\"\n            + \"            \\t\\t}\\n\"\n            + \"                }\\n\"\n            + \"            });\\n\"\n            + \"\\t\\t\\t</script>\\n\"\n            + \"\\t\\t</div>\\n\"\n            + \"\\t</div>\\t\\n\"\n            + \"\\t<script type=\\\"text/javascript\\\" src=\\\"/js/syntax-highlighter-2.1.382/scripts/brush.js\\\"></script>\\n\"\n            + \"<link type=\\\"text/css\\\" rel=\\\"stylesheet\\\" href=\\\"/js/syntax-highlighter-2.1.382/styles/shCore.css\\\"/>\\n\"\n            + \"<link type=\\\"text/css\\\" rel=\\\"stylesheet\\\" href=\\\"/js/syntax-highlighter-2.1.382/styles/shThemeDefault.css\\\"/>\\n\"\n            + \"<script type='text/javascript'><!--\\n\"\n            + \"$(document).ready(function(){\\n\"\n            + \"\\tSyntaxHighlighter.config.clipboardSwf = '/js/syntax-highlighter-2.1.382/scripts/clipboard.swf';\\n\"\n            + \"\\tSyntaxHighlighter.all();\\n\"\n            + \"});\\n\"\n            + \"//-->\\n\"\n            + \"</script>\\n\"\n            + \"\\t<div class='QuestionRelations'>\\n\"\n            + \"\\t <div style='text-align:center;margin-bottom:10px;'>\\n\"\n            + \"    \\t<a href=\\\"http://www.oschina.net/action/visit/ad?id=1033\\\" target=\\\"_blank\\\" title=\\\"JPush——极光推送\\\"><img src=\\\"http://static.oschina.net/uploads/space/2013/0319/103739_17pH_179699.jpg\\\"/></a>\\n\"\n            + \"\\t</div>\\n\"\n            + \"\\t\\t<div id='QuestionWizard'>\\n\"\n            + \"\\t\\t\\t有什么技术问题吗？\\n\"\n            + \"\\t\\t\\t<a href='/question/ask' class='rndbutton'><span>我要提问</span></a>\\n\"\n            + \"\\t\\t\\t<div class='clear'></div>\\n\"\n            + \"\\t\\t</div>\\n\"\n            + \"\\t\\t\\n\"\n            + \"\\t\\t\\t\\t\\t\\t<div id='OtherQuestionsOfUser' class='Qlist'>\\n\"\n            + \"\\t\\t\\t<strong><a href=\\\"http://my.oschina.net/u/818848/?ft=bbs&scope=2&showme=1\\\" class=\\\"more\\\">全部(29)...</a><em>午后冬日</em>的其他问题</strong>\\n\"\n            + \"\\t\\t\\t<ul>\\n\"\n            + \"\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/818848_106829\\\" title=\\\"是jsf还是ajax框架，这是个问题\\\">是jsf还是ajax框架，这是个问题</a><span class='date'>(4回/194阅,4天前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/818848_106805\\\" title=\\\"关于ireport的问题\\\">关于ireport的问题</a><span class='date'>(0回/4阅,4天前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/818848_106539\\\" title=\\\"关于JasperReports的问题\\\">关于JasperReports的问题</a><span class='date'>(2回/47阅,6天前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/818848_105917\\\" title=\\\"IDEA代码编辑窗口能不能上下分屏\\\">IDEA代码编辑窗口能不能上下分屏</a><span class='date'>(2回/53阅,10天前)</span></li>\\t\\t\\t\\t\\t\\t</ul>\\n\"\n            + \"\\t\\t</div>\\n\"\n            + \"\\t\\t\\t\\t<div style='text-align:center;margin-top:20px;'>\\n\"\n            + \"\\t\\t<script type=\\\"text/javascript\\\" src=\\\"/js/ad/question.js\\\"></script>\\n\"\n            + \"\\t\\t</div>\\n\"\n            + \"\\t\\t\\n\"\n            + \"\\t\\t<div id='Similarity' class='Qlist'>\\n\"\n            + \"\\t\\t\\t<strong>类似的话题</strong>\\n\"\n            + \"\\t\\t\\t<ul>\\n\"\n            + \"        \\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/267632_49688\\\" title=\\\"jQuery easyUI 分页(Pagination)用法\\\">jQuery easyUI 分页(Pagination)用法</a><span class='date'>(2回/1228阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/138848_49846\\\" title=\\\"谨慎使用EasyUI\\\">谨慎使用EasyUI</a><span class='date'>(1回/1361阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/205548_31992\\\" title=\\\"easyui datagird 初始化加载俩次\\\">easyui datagird 初始化加载俩次</a><span class='date'>(6回/690阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/84535_32061\\\" title=\\\"打算使用easyui,求源码,求建议~\\\">打算使用easyui,求源码,求建议~</a><span class='date'>(17回/4105阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/201422_32682\\\" title=\\\"jquery easyui form 有没好的设计\\\">jquery easyui form 有没好的设计</a><span class='date'>(2回/812阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/183509_32885\\\" title=\\\"jeasyui 中combobox的onselect事件怎么做下拉框的级联\\\">jeasyui 中combobox的onselect事件怎么做下拉框的级联</a><span class='date'>(1回/741阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/161511_36411\\\" title=\\\"easyui  treegrid行编辑 效率慢怎么解决?\\\">easyui  treegrid行编辑 效率慢怎么解决?</a><span class='date'>(1回/671阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/166022_24392\\\" title=\\\"ComboGrid怎么动态绑定ds呢？\\\">ComboGrid怎么动态绑定ds呢？</a><span class='date'>(2回/427阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/146658_24974\\\" title=\\\"easyui中怎么显示一个list的数据？\\\">easyui中怎么显示一个list的数据？</a><span class='date'>(1回/594阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/97507_26165\\\" title=\\\"jquery easyui 組件無法顯示\\\">jquery easyui 組件無法顯示</a><span class='date'>(2回/671阅,1年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/46586_16818\\\" title=\\\"关于jQuery EasyUI Form的问题\\\">关于jQuery EasyUI Form的问题</a><span class='date'>(2回/1103阅,2年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/59256_17359\\\" title=\\\"jquery easyUI \\\">jquery easyUI </a><span class='date'>(1回/557阅,2年前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/188775_77569\\\" title=\\\"easyui中有没有点击datagrid的一个单元格 ，就使这个单元格变成可编辑的办法呢 \\\">easyui中有没有点击datagrid的一个单元格 ，就使这个单元格变成可编辑的办法呢 </a><span class='date'>(1回/890阅,5个月前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/256315_79207\\\" title=\\\"jQuery easyui：点击tree控件后无法获取node属性\\\">jQuery easyui：点击tree控件后无法获取node属性</a><span class='date'>(3回/533阅,5个月前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/580112_79227\\\" title=\\\"EasyUI 中tab标签的选项卡 置于左边是怎么实现的\\\">EasyUI 中tab标签的选项卡 置于左边是怎么实现的</a><span class='date'>(5回/426阅,5个月前)</span></li>\\t\\t\\t\\t\\t\\t<li><a href=\\\"http://www.oschina.net/question/868642_79423\\\" title=\\\"easyui无法跳转到 指定action   各位帮忙看看哪里有问题么？\\\">easyui无法跳转到 指定action   各位帮忙看看哪里有问题么？</a><span class='date'>(2回/162阅,5个月前)</span></li>\\t\\t\\t\\t\\t\\t</ul>\\n\"\n            + \"\\t\\t</div>\\n\"\n            + \"\\t</div>\\n\"\n            + \"\\t<div class='clear'></div>   \\n\"\n            + \"</div>\\n\"\n            + \"<script type='text/javascript' src='/js/ke/kindeditor-min.js?v=4.1.4' charset='utf-8'></script>\\n\"\n            + \"<script type='text/javascript'>\\n\"\n            + \"<!--\\n\"\n            + \"var editor;\\n\"\n            + \"KindEditor.ready(function(K) {\\n\"\n            + \"    editor = K.create('#txt_answner', {\\n\"\n            + \"\\t\\tthemeType : 'oschina',\\n\"\n            + \"\\t\\tresizeType : 1,\\n\"\n            + \"\\t\\turlType: 'domain',\\n\"\n            + \"\\t\\tshadowMode : false,\\n\"\n            + \"\\t\\tallowPreviewEmoticons : false,\\n\"\n            + \"\\t\\tallowImageUpload : true,\\n\"\n            + \"\\t\\tallowFlashUpload : false,\\n\"\n            + \"\\t\\tcssPath : '/css/ke-oschina.css',\\n\"\n            + \"\\t\\tuploadJson : '/action/blog/upload_img',\\n\"\n            + \"\\t\\tafterCreate : function(){\\n\"\n            + \"\\t\\t\\t/*\\n\"\n            + \"\\t\\t\\tK.ctrl(this.edit.iframe.get(0).contentWindow.document, 13, function() {\\n\"\n            + \"\\t\\t\\t\\t$(\\\"#txt_answner\\\").parent().submit();\\n\"\n            + \"\\t\\t\\t});\\n\"\n            + \"\\t\\t\\t*/\\n\"\n            + \"\\t\\t\\t$(this.edit.iframe.get(0).contentWindow.document).keydown(function(e) {\\n\"\n            + \"\\t\\t\\t\\tif ((e.ctrlKey || e.metaKey) && e.which == 13 && !e.shiftKey && !e.altKey){\\n\"\n            + \"\\t\\t\\t\\t\\t$(\\\"#txt_answner\\\").parent().submit();\\n\"\n            + \"\\t\\t\\t\\t}\\n\"\n            + \"\\t\\t\\t});\\n\"\n            + \"\\t\\t},\\n\"\n            + \"\\t\\tafterChange : function() {\\n\"\n            + \"\\t\\t\\tthis.sync();\\n\"\n            + \"\\t\\t},\\n\"\n            + \"\\t\\titems : ['bold', 'italic', 'underline', 'strikethrough', 'removeformat','|','insertorderedlist', 'insertunorderedlist', \\n\"\n            + \"\\t\\t\\t\\t 'forecolor', 'hilitecolor', 'fontname', 'fontsize',  '|', 'link', 'unlink', 'emoticons', \\n\"\n            + \"\\t\\t\\t\\t 'shcode', 'image', 'flash', 'quote', '|', 'source','about'],\\n\"\n            + \"\\t\\thtmlTags:\\n\"\n            + \"\\t\\t{\\n\"\n            + \"\\t\\t\\tscript : ['src'],\\n\"\n            + \"            font : ['color', 'size', 'face', '.background-color'],\\n\"\n            + \"            span : [\\n\"\n            + \"                    '.color', '.background-color', '.font-size', '.font-family', '.background',\\n\"\n            + \"                    '.font-weight', '.font-style', '.text-decoration', '.vertical-align', '.line-height'\\n\"\n            + \"            ],\\n\"\n            + \"            div : [\\n\"\n            + \"                    'class', 'align', '.border', '.margin', '.padding', '.text-align', '.color',\\n\"\n            + \"                    '.background-color', '.font-size', '.font-family', '.font-weight', '.background',\\n\"\n            + \"                    '.font-style', '.text-decoration', '.vertical-align', '.margin-left'\\n\"\n            + \"            ],\\n\"\n            + \"            table: [\\n\"\n            + \"                    'border', 'cellspacing', 'cellpadding', 'width', 'height', 'align', 'bordercolor',\\n\"\n            + \"                    '.padding', '.margin', '.border', 'bgcolor', '.text-align', '.color', '.background-color',\\n\"\n            + \"                    '.font-size', '.font-family', '.font-weight', '.font-style', '.text-decoration', '.background',\\n\"\n            + \"                    '.width', '.height', '.border-collapse'\\n\"\n            + \"            ],\\n\"\n            + \"            'td,th': [\\n\"\n            + \"                    'align', 'valign', 'width', 'height', 'colspan', 'rowspan', 'bgcolor',\\n\"\n            + \"                    '.text-align', '.color', '.background-color', '.font-size', '.font-family', '.font-weight',\\n\"\n            + \"                    '.font-style', '.text-decoration', '.vertical-align', '.background', '.border'\\n\"\n            + \"            ],\\n\"\n            + \"            a : ['href', 'target', 'name'],\\n\"\n            + \"            embed : ['src', 'width', 'height', 'type', 'loop', 'autostart', 'quality', '.width', '.height', 'align', 'allowscriptaccess'],\\n\"\n            + \"            img : ['src', 'width', 'height', 'border', 'alt', 'title', 'align', '.width', '.height', '.border'],\\n\"\n            + \"            'p,ol,ul,li,blockquote,h1,h2,h3,h4,h5,h6' : [\\n\"\n            + \"                    'align', '.text-align', '.color', '.background-color', '.font-size', '.font-family', '.background',\\n\"\n            + \"                    '.font-weight', '.font-style', '.text-decoration', '.vertical-align', '.text-indent', '.margin-left'\\n\"\n            + \"            ],\\n\"\n            + \"            pre : ['class'],\\n\"\n            + \"            hr : ['class', '.page-break-after'],\\n\"\n            + \"            'br,tbody,tr,strong,b,sub,sup,em,i,u,strike,s,del' : []\\n\"\n            + \"\\t\\t}\\n\"\n            + \"    });\\n\"\n            + \"});\\n\"\n            + \"//-->\\n\"\n            + \"</script>\\n\"\n            + \"<!--[if lt IE 7]>\\n\"\n            + \"<script type=\\\"text/javascript\\\" src=\\\"/js/minmax.js\\\"></script>\\n\"\n            + \"<![endif]-->\\n\"\n            + \"<script type=\\\"text/javascript\\\" src=\\\"/action/visit/question?id=107307\\\"></script>\\n\"\n            + \"<script type='text/javascript'>\\n\"\n            + \"<!--\\n\"\n            + \"$(document).ready(function() {\\n\"\n            + \"\\t$('.Answer .replies li').hover(\\n\"\n            + \"\\t\\tfunction(){$(this).addClass('hover');},\\n\"\n            + \"\\t\\tfunction(){$(this).removeClass('hover');}\\n\"\n            + \"\\t);\\t\\n\"\n            + \"\\t\\n\"\n            + \"    $('.detail img').css('cursor','pointer');\\n\"\n            + \"    jQuery.each($('.detail img'),function(idx,v){\\n\"\n            + \"    \\t$(v).wrap(\\\"<a href='\\\"+$(this).attr('src')+\\\"' target='_blank'></a>\\\");\\n\"\n            + \"    });\\n\"\n            + \"\\t\\n\"\n            + \"\\t$('#c').bind('mouseover mouseout',function(){\\n\"\n            + \"\\t\\t$('#c_on').toggle();\\n\"\n            + \"\\t\\t$('#c_off').toggle();\\n\"\n            + \"\\t});\\n\"\n            + \"\\t\\n\"\n            + \"\\t$('#favor_trigger').click(function(){\\n\"\n            + \"\\t\\t\\tadd_to_favor(107307,2);\\n\"\n            + \"\\t\\t});\\n\"\n            + \"});\\n\"\n            + \"function ask_too(qid, ask){\\n\"\n            + \"\\tajax_post(\\\"/action/question/ask_too?id=\\\"+qid+\\\"&ask=\\\"+ask,\\\"\\\",function(html){\\n\"\n            + \"\\t\\tjson = eval('('+html+')');\\n\"\n            + \"\\t\\tif(json.asker_count >= 0){\\n\"\n            + \"\\t\\t\\t$('#c_asker_count').html(json.asker_count);\\n\"\n            + \"\\t\\t\\tif(json.ask_mode)\\n\"\n            + \"\\t\\t\\t\\t$('#RQuestionAction').html(\\\"<span class='rect'>已问同一问题 | <a href='javascript:ask_too(107307,false)'>取消？</a></span>\\\");\\n\"\n            + \"\\t\\t\\telse\\n\"\n            + \"\\t\\t\\t\\t$('#RQuestionAction').html(\\\"<a href='javascript:ask_too(107307,true)' class='rndbutton'><span>我想问同样的问题</span></a>\\\");\\t\\t\\t\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t\\telse{\\n\"\n            + \"\\t\\t\\t$('#RQuestionAction').poshytip({\\n\"\n            + \"\\t\\t\\t\\tclassName: 'tip-yellowsimple',\\n\"\n            + \"\\t\\t\\t\\tcontent: json.msg,\\n\"\n            + \"\\t\\t\\t\\tshowOn: 'none',\\n\"\n            + \"\\t\\t\\t\\talignTo: 'target',\\n\"\n            + \"\\t\\t\\t\\talignX: 'center',\\n\"\n            + \"\\t\\t\\t\\talignY: 'top',\\n\"\n            + \"\\t\\t\\t\\toffsetY: 6\\n\"\n            + \"\\t\\t\\t});\\n\"\n            + \"\\t\\t\\t$('#RQuestionAction').poshytip('show');\\n\"\n            + \"\\t\\t\\tvar t = setTimeout(function(){\\n\"\n            + \"\\t\\t\\t\\tclearTimeout(t);\\n\"\n            + \"\\t\\t\\t\\t$('#RQuestionAction').poshytip('destroy');\\n\"\n            + \"\\t\\t\\t},4000);\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"function delete_q(qid){\\n\"\n            + \"\\tif(!confirm(\\\"您确认要删除此问题吗，删除的数据不可恢复？\\\"))\\n\"\n            + \"\\t\\treturn ;\\n\"\n            + \"\\t\\tajax_post(\\\"/action/question/delete?id=\\\"+qid+\\\"&hash=-500641190\\\",\\\"\\\",function(html){\\n\"\n            + \"\\t\\tif(html.length>0)\\n\"\n            + \"\\t\\t\\talert(html);\\n\"\n            + \"\\t\\telse{\\n\"\n            + \"\\t\\t\\tlocation.href = \\\"/question\\\";\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"function edit_answer(aid){\\n\"\n            + \"\\tlocation.href=\\\"/question/edit_answer?id=\\\"+aid;\\n\"\n            + \"}\\n\"\n            + \"function delete_answer(aid,hash){\\n\"\n            + \"\\tif(!confirm(\\\"您确认要删除此答案吗，删除的数据不可恢复？\\\"))\\n\"\n            + \"\\t\\treturn ;\\n\"\n            + \"\\tajax_post(\\\"/action/question/delete_answer?id=\\\"+aid+\\\"&hash=\\\"+hash,\\\"\\\",function(html){\\n\"\n            + \"\\t\\tif(html.length>0)\\n\"\n            + \"\\t\\t\\talert(html);\\n\"\n            + \"\\t\\telse{\\n\"\n            + \"\\t\\t\\t$('#answer_'+aid).fadeOut();\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"function delete_post_reply(aid,hash){\\n\"\n            + \"\\tif(!confirm(\\\"您确认要删除此评论吗，删除的数据不可恢复？\\\"))\\n\"\n            + \"\\t\\treturn ;\\n\"\n            + \"\\tajax_post(\\\"/action/question/delete_answer?id=\\\"+aid+\\\"&hash=\\\"+hash,\\\"\\\",function(html){\\n\"\n            + \"\\t\\tif(html.length>0)\\n\"\n            + \"\\t\\t\\talert(html);\\n\"\n            + \"\\t\\telse{\\n\"\n            + \"\\t\\t\\t$('#PostReply_'+aid).fadeOut();\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"function delete_q_rpl(qid, rid){\\n\"\n            + \"\\tif(!confirm(\\\"删除补充说明会被扣威望值，是否继续？\\\"))\\n\"\n            + \"\\t\\treturn ;\\n\"\n            + \"\\tajax_post(\\\"/action/question/delete_detail?id=\\\"+rid,\\\"\\\",function(html){\\n\"\n            + \"\\t\\tif(html.length>0)\\n\"\n            + \"\\t\\t\\talert(html);\\n\"\n            + \"\\t\\telse\\n\"\n            + \"\\t\\t\\t$(\\\"#q_reply_\\\"+rid).fadeOut();\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"function close_tip(tid){$('#'+tid).poshytip('destroy');}\\n\"\n            + \"//答案投票\\n\"\n            + \"function vote_answer(qid, vote_up, need_confirm){\\n\"\n            + \"\\tif(need_confirm && !vote_up){\\n\"\n            + \"\\t\\tif(!$('#a_post_votedown_' + qid).hasClass('bold')){\\n\"\n            + \"\\t\\t\\tvar vote_down_confirm_msg = \\\"<p>此操作将会扣掉你1个积分，是否继续？</p><p style='margin-top:10px;'><a href='javascript:vote_answer(\\\"+qid+\\\",false,false)' class='rbtn' style='margin:0 10px 0 50px;'><span>确定</span></a><a href=\\\\\\\"javascript:close_tip('a_post_votedown_\\\" + qid +\\\"')\\\\\\\" class='rbtn'><span>取消</span></a></p>\\\";\\t\\t\\t\\n\"\n            + \"\\t\\t\\t$('#a_post_votedown_' + qid).poshytip({\\n\"\n            + \"\\t\\t\\t\\tclassName: 'tip-yellowsimple',\\n\"\n            + \"\\t\\t\\t\\tcontent: vote_down_confirm_msg,\\n\"\n            + \"\\t\\t\\t\\tshowOn: 'none',\\n\"\n            + \"\\t\\t\\t\\tslide: false,\\n\"\n            + \"\\t\\t\\t\\tfade: false,\\n\"\n            + \"\\t\\t\\t\\talignTo: 'target',\\n\"\n            + \"\\t\\t\\t\\talignX: 'center',\\n\"\n            + \"\\t\\t\\t\\toffsetY: 8\\n\"\n            + \"\\t\\t\\t});\\n\"\n            + \"\\t\\t\\t$('#a_post_votedown_' + qid).poshytip('show');\\n\"\n            + \"\\t\\t\\treturn;\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t}\\n\"\n            + \"\\tif(!need_confirm){\\n\"\n            + \"\\t\\t$('#a_post_votedown_' + qid).poshytip('destroy');\\n\"\n            + \"\\t}\\n\"\n            + \"\\tajax_post(\\\"/action/question/vote_answer?id=\\\"+qid+\\\"&vote=\\\"+vote_up+\\\"&user=190591\\\",\\\"\\\",function(data){\\n\"\n            + \"\\t\\tjson = eval('('+data+')');\\n\"\n            + \"\\t\\tif(json.msg){\\n\"\n            + \"\\t\\t\\tvar aid = vote_up?\\\"a_post_voteup_\\\":\\\"a_post_votedown_\\\";\\n\"\n            + \"\\t\\t\\taid += qid;\\n\"\n            + \"\\t\\t\\t$('#'+aid).poshytip({\\n\"\n            + \"\\t\\t\\t\\tclassName: 'tip-yellowsimple',\\n\"\n            + \"\\t\\t\\t\\tcontent: json.msg,\\n\"\n            + \"\\t\\t\\t\\tshowOn: 'none',\\n\"\n            + \"\\t\\t\\t\\talignTo: 'target',\\n\"\n            + \"\\t\\t\\t\\talignX: 'center',\\n\"\n            + \"\\t\\t\\t\\talignY: 'top',\\n\"\n            + \"\\t\\t\\t\\toffsetY: 6\\n\"\n            + \"\\t\\t\\t});\\n\"\n            + \"\\t\\t\\t$('#'+aid).poshytip('show');\\n\"\n            + \"\\t\\t\\tvar t = setTimeout(function(){\\n\"\n            + \"\\t\\t\\t\\tclearTimeout(t);\\n\"\n            + \"\\t\\t\\t\\t$('#'+aid).poshytip('destroy');\\n\"\n            + \"\\t\\t\\t},2000);\\n\"\n            + \"\\t\\t\\t//jQuery.fancybox(\\\"<div class='error_box'>\\\"+json.msg+\\\"</div>\\\");\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t\\telse{\\n\"\n            + \"\\t\\t\\tif(vote_up){\\n\"\n            + \"\\t\\t\\t\\t$('#post_voteup_'+qid).html('('+json.vote+')');\\n\"\n            + \"\\t\\t\\t\\t$('#a_post_voteup_'+qid).toggleClass('bold');\\n\"\n            + \"\\t\\t\\t}\\n\"\n            + \"\\t\\t\\telse{\\n\"\n            + \"\\t\\t\\t\\t$('#post_votedown_'+qid).html('('+json.vote+')');\\n\"\n            + \"\\t\\t\\t\\t$('#a_post_votedown_'+qid).toggleClass('bold');\\n\"\n            + \"\\t\\t\\t}\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"//问题投票 \\n\"\n            + \"$('#vote_up').click(function(){\\n\"\n            + \"\\tif(this.clickTimeout){\\n\"\n            + \"        // 双击\\n\"\n            + \"        clearTimeout(this.clickTimeout);\\n\"\n            + \"        this.clickTimeout = null;\\n\"\n            + \"\\t\\talert(\\\"不用那么费劲啦，点击一下就够了:)\\\");\\n\"\n            + \"    }\\n\"\n            + \"    else{\\n\"\n            + \"        // 单击\\n\"\n            + \"        var elem = this;\\n\"\n            + \"        this.clickTimeout = setTimeout(function(){\\n\"\n            + \"            // 执行点击动作\\n\"\n            + \"            elem.clickTimeout = null;\\n\"\n            + \"\\t\\t\\tvote_question(107307,true, true);\\n\"\n            + \"        }, 250);\\n\"\n            + \"    }\\n\"\n            + \"    //阻止链接onclick时的默认行为\\n\"\n            + \"    return false;\\n\"\n            + \"});\\n\"\n            + \"$('#vote_down').click(function(){\\n\"\n            + \"\\tvote_question(107307,false, true);\\n\"\n            + \"\\t/*\\n\"\n            + \"\\tif(this.clickTimeout){\\n\"\n            + \"        // 双击\\n\"\n            + \"        clearTimeout(this.clickTimeout);\\n\"\n            + \"        this.clickTimeout = null;\\n\"\n            + \"\\t\\talert(\\\"不用那么费劲啦，点击一下就够了:)\\\");\\n\"\n            + \"    }\\n\"\n            + \"    else{\\n\"\n            + \"        // 单击\\n\"\n            + \"        var elem = this;\\n\"\n            + \"        this.clickTimeout = setTimeout(function(){\\n\"\n            + \"            // 执行点击动作\\n\"\n            + \"            elem.clickTimeout = null;\\n\"\n            + \"\\t\\t\\tvote_question(107307,false, true);\\n\"\n            + \"        }, 250);\\n\"\n            + \"    }\\n\"\n            + \"    //阻止链接onclick时的默认行为\\n\"\n            + \"\\t*/\\n\"\n            + \"    return false;\\n\"\n            + \"});\\n\"\n            + \"function vote_question(qid, vote_up, need_confirm){\\n\"\n            + \"\\tif(need_confirm && !vote_up){\\n\"\n            + \"\\t\\tif($('#Vote #vote_down').hasClass('ask_vote_down')){\\n\"\n            + \"\\t\\t\\tvar vote_down_confirm_msg = \\\"<p>踩问题将会扣掉你1个积分，是否继续？</p><p style='margin-top:10px;'><a href='javascript:vote_question(107307,false,false)' class='rbtn' style='margin-right:10px;'><span>确定</span></a><a href=\\\\\\\"javascript:close_tip('vote_down')\\\\\\\" class='rbtn'><span>取消</span></a></p>\\\";\\n\"\n            + \"\\t\\t\\t$('#Vote #vote_down').poshytip({\\n\"\n            + \"\\t\\t\\t\\tclassName: 'tip-yellowsimple',\\n\"\n            + \"\\t\\t\\t\\tcontent: vote_down_confirm_msg,\\n\"\n            + \"\\t\\t\\t\\tslide: false,\\n\"\n            + \"\\t\\t\\t\\tfade: false,\\n\"\n            + \"\\t\\t\\t\\tshowOn: 'none',\\n\"\n            + \"\\t\\t\\t\\talignTo: 'target',\\n\"\n            + \"\\t\\t\\t\\talignX: 'inner-right',\\n\"\n            + \"\\t\\t\\t\\talignY: 'bottom',\\n\"\n            + \"\\t\\t\\t\\toffsetX: -30,\\n\"\n            + \"\\t\\t\\t\\toffsetY: 15\\n\"\n            + \"\\t\\t\\t});\\n\"\n            + \"\\t\\t\\t$('#Vote #vote_down').poshytip('show');\\n\"\n            + \"\\t\\t\\treturn;\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t}\\n\"\n            + \"\\tif(!need_confirm){\\n\"\n            + \"\\t\\t$('#Vote #vote_down').poshytip('destroy');\\n\"\n            + \"\\t}\\n\"\n            + \"\\tajax_post(\\\"/action/question/vote?user=190591&id=\\\"+qid+\\\"&vote=\\\"+vote_up,\\\"\\\",function(data){\\n\"\n            + \"\\t\\tjson = eval('('+data+')');\\n\"\n            + \"\\t\\tif(json.msg){\\n\"\n            + \"\\t\\t\\tvar aid = vote_up?\\\"vote_up\\\":\\\"vote_down\\\";\\n\"\n            + \"\\t\\t\\t$('#'+aid).poshytip({\\n\"\n            + \"\\t\\t\\t\\tclassName: 'tip-yellowsimple',\\n\"\n            + \"\\t\\t\\t\\tcontent: json.msg,\\n\"\n            + \"\\t\\t\\t\\tshowOn: 'none',\\n\"\n            + \"\\t\\t\\t\\talignTo: 'target',\\n\"\n            + \"\\t\\t\\t\\talignX: 'inner-right',\\n\"\n            + \"\\t\\t\\t\\talignY: 'bottom',\\n\"\n            + \"\\t\\t\\t\\toffsetX: 5,\\n\"\n            + \"\\t\\t\\t\\toffsetX: -35\\n\"\n            + \"\\t\\t\\t});\\n\"\n            + \"\\t\\t\\t$('#'+aid).poshytip('show');\\n\"\n            + \"\\t\\t\\tvar t = setTimeout(function(){\\n\"\n            + \"\\t\\t\\t\\tclearTimeout(t);\\n\"\n            + \"\\t\\t\\t\\t$('#'+aid).poshytip('destroy');\\n\"\n            + \"\\t\\t\\t},2000);\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t\\telse{\\n\"\n            + \"\\t\\t\\t\\t\\t\\tif(vote_up){\\n\"\n            + \"\\t\\t\\t\\t$('#Vote .vote-up-count').html(json.vote_up);\\n\"\n            + \"\\t\\t\\t\\t$('#Vote #vote_up').toggleClass('ask_vote_up');\\n\"\n            + \"\\t\\t\\t\\t$('#Vote #vote_up').toggleClass('ask_vote_uped');\\n\"\n            + \"\\t\\t\\t}\\n\"\n            + \"\\t\\t\\telse{\\n\"\n            + \"\\t\\t\\t\\t$('#Vote .vote-down-count').html(json.vote_down);\\n\"\n            + \"\\t\\t\\t\\t$('#Vote #vote_down').toggleClass('ask_vote_down');\\n\"\n            + \"\\t\\t\\t\\t$('#Vote #vote_down').toggleClass('ask_vote_downed');\\n\"\n            + \"\\t\\t\\t}\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"//评论答案\\n\"\n            + \"function reply_to_post(postid,uid){\\n\"\n            + \"\\tpopup(\\\"/action/ajax/reply_to_post?id=\\\" + postid + \\\"&refer=\\\"+uid);\\n\"\n            + \"\\t}\\n\"\n            + \"\\n\"\n            + \"function show_rp_next(postid,current,total){\\n\"\n            + \"\\tif(current < total){\\n\"\n            + \"\\t\\tvar next_page = current + 1;\\n\"\n            + \"    \\tvar url = \\\"/question/post_replies?answer=\\\"+postid+\\\"&rp=\\\"+next_page;\\n\"\n            + \"    \\tajax_post(url,\\\"\\\",function(html){\\n\"\n            + \"    \\t\\t$('#PostReplies_'+postid).html(html);\\n\"\n            + \"\\t\\t\\t\\taddRepliesHoverEvent();\\n\"\n            + \"\\t\\t\\t});\\n\"\n            + \"\\t}\\n\"\n            + \"}\\n\"\n            + \"\\n\"\n            + \"function addRepliesHoverEvent(){\\n\"\n            + \"\\t$(\\\"li [id ^= 'PostReply']\\\").hover(function(){\\n\"\n            + \"\\t\\t$(this).addClass(\\\"hover\\\");\\n\"\n            + \"\\t},function(){\\n\"\n            + \"\\t\\t$(this).removeClass(\\\"hover\\\");\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"\\n\"\n            + \"function show_rp_prev(postid,current,total){\\n\"\n            + \"\\tif(current > 1){\\n\"\n            + \"\\t\\tvar next_page = current - 1;\\n\"\n            + \"    \\tvar url = \\\"/question/post_replies?answer=\\\"+postid+\\\"&rp=\\\"+next_page;\\n\"\n            + \"    \\tajax_post(url,\\\"\\\",function(html){\\n\"\n            + \"    \\t\\t$('#PostReplies_'+postid).html(html);\\n\"\n            + \"\\t\\t\\taddRepliesHoverEvent();\\n\"\n            + \"    \\t});\\n\"\n            + \"\\t}\\n\"\n            + \"}\\n\"\n            + \"function mark_as_top(qid, as_top) {\\n\"\n            + \"\\tvar args = \\\"id=\\\"+qid+\\\"&top=\\\"+as_top;\\n\"\n            + \"\\tajax_post(\\\"/action/question/mark_as_top\\\",args,function(html){\\n\"\n            + \"\\t\\talert(html);\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"function mark_as_best(postid, is_best){\\n\"\n            + \"\\tvar args = \\\"id=\\\"+postid+\\\"&best=\\\"+(is_best?1:0);\\n\"\n            + \"\\tajax_post(\\\"/action/question/mark_as_best\\\",args,function(html){\\n\"\n            + \"    \\tif(html.length>0){\\n\"\n            + \"    \\t\\t$('#best_answer_'+postid).poshytip({\\n\"\n            + \"    \\t\\t\\tclassName: 'tip-yellowsimple',\\n\"\n            + \"    \\t\\t\\tcontent: html,\\n\"\n            + \"    \\t\\t\\tshowOn: 'none',\\n\"\n            + \"    \\t\\t\\talignTo: 'target',\\n\"\n            + \"    \\t\\t\\talignX: 'center',\\n\"\n            + \"    \\t\\t\\talignY: 'top',\\n\"\n            + \"    \\t\\t\\toffsetY: 6\\n\"\n            + \"    \\t\\t});\\n\"\n            + \"    \\t\\t$('#best_answer_'+postid).poshytip('show');\\n\"\n            + \"    \\t\\tvar t = setTimeout(function(){\\n\"\n            + \"    \\t\\t\\tclearTimeout(t);\\n\"\n            + \"    \\t\\t\\t$('#best_answer_'+postid).poshytip('destroy');\\n\"\n            + \"    \\t\\t},2000);\\n\"\n            + \"    \\t}\\n\"\n            + \"    \\telse{\\n\"\n            + \"\\t\\t\\tif(is_best)\\n\"\n            + \"\\t\\t\\t\\t$('#answer_'+postid).addClass('Best');\\n\"\n            + \"\\t\\t\\telse\\n\"\n            + \"\\t\\t\\t\\t$('#answer_'+postid).removeClass('Best');\\n\"\n            + \"    \\t}\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"function edit_tags(qid){\\n\"\n            + \"\\tpopup(\\\"/question/edit_tags?question=\\\"+qid);\\n\"\n            + \"}\\n\"\n            + \"function edit_catalogs(qid){\\n\"\n            + \"\\tpopup(\\\"/admin/catalog/set-catalogs?parent=1&type=2&id=\\\"+qid);\\n\"\n            + \"}\\n\"\n            + \"function event_apply(event_id){\\t\\n\"\n            + \"\\tpopup(\\\"/action/ajax/event_apply\\\",\\\"id=\\\"+event_id);\\n\"\n            + \"}\\n\"\n            + \"function cancel_apply(event_id){\\n\"\n            + \"\\tif(confirm(\\\"您确认要取消参加此次活动吗？\\\")){\\n\"\n            + \"\\t\\tajax_post(\\\"/action/event/cancel\\\",\\\"event=\\\"+event_id,function(html){\\n\"\n            + \"\\t\\t\\tif(html.length>0)\\n\"\n            + \"\\t\\t\\t\\talert(html);\\n\"\n            + \"\\t\\t\\telse\\n\"\n            + \"\\t\\t\\t\\talert('已取消参加此次活动，感谢您的支持:)');\\n\"\n            + \"\\t\\t});\\n\"\n            + \"\\t}\\n\"\n            + \"}\\n\"\n            + \"\\n\"\n            + \"var favor_ok = \\\"<p class='favor_ok'>已成功添加到收藏夹<br><br> <a href='http://my.oschina.net/flashsword/favorites?type=$DAISY_OBJ_TYPE'>我的收藏夹</a> | <a href='javascript:close_favor()'>关闭</a></p>\\\";\\n\"\n            + \"function delete_favor(obi_id, obj_type){\\n\"\n            + \"\\tif(!confirm('确定取消收藏？')) return;\\n\"\n            + \"\\t$.post(\\\"/action/favorite/cancel?type=\\\"+obj_type+\\\"&id=\\\"+obi_id+\\\"&user=190591\\\",function(html){\\n\"\n            + \"\\t\\t$('#favor_trigger').parent('div').removeClass('ask_collected').addClass('ask_collect');\\n\"\n            + \"\\t\\t$('#favor_trigger').attr('title','添加到收藏');\\n\"\n            + \"    \\t$('#p_favor_count').html(html);\\n\"\n            + \"\\t\\t$('#favor_trigger').unbind('click');\\n\"\n            + \"\\t\\t$('#favor_trigger').click(function(){add_to_favor(107307,2);});\\n\"\n            + \"\\t});\\n\"\n            + \"}\\n\"\n            + \"function add_to_favor(obj_id, obj_type){\\n\"\n            + \"    var dlg_favor = \\\"<div id='favor_form'><p>多个标签使用逗号(,)隔开，最多三个</p><form action='/action/favorite/add?user=190591' height='60px' width='200px' method='POST'>\\\";\\n\"\n            + \"\\tdlg_favor += \\\"<input type='hidden' name='id' value='\\\"+obj_id+\\\"'/>\\\";\\n\"\n            + \"\\tdlg_favor += \\\"<input type='hidden' name='type' value='\\\"+obj_type+\\\"'/>\\\";\\n\"\n            + \"\\tdlg_favor += \\\"<input type='text' name='tags' size='25' class='_favor_input' id='_favor_tags'/>\\\";\\n\"\n            + \"\\tdlg_favor += \\\"<input type='submit' value='收藏' class='_favor_button'/><input type='button' value='取消' onclick='close_favor();' class='_favor_button'/><a id='TagsSwitcher' state='off'>选取标签↓</a></form>\\\";\\n\"\n            + \"\\tdlg_favor += \\\"<div id='MyTags' ></div></div>\\\";\\n\"\n            + \"    $('#favor_trigger').poshytip('destroy');\\n\"\n            + \"    $('#favor_trigger').poshytip({\\n\"\n            + \"    \\tclassName: 'tip-yellowsimple',\\n\"\n            + \"    \\tcontent: dlg_favor,\\n\"\n            + \"    \\tshowOn: 'none',\\n\"\n            + \"    \\talignTo: 'target',\\t\\n\"\n            + \"\\t\\talignX: 'inner-right',\\n\"\n            + \"\\t\\talignY: 'bottom',\\n\"\n            + \"\\t\\toffsetX: -20,\\n\"\n            + \"\\t\\toffsetY: 15\\n\"\n            + \"    });\\n\"\n            + \"    $('#favor_trigger').poshytip('show');\\n\"\n            + \"\\t$('#_favor_tags').focus();\\n\"\n            + \"\\t$('#favor_form form').ajaxForm({\\n\"\n            + \"\\t\\tsuccess: function(html) {\\n\"\n            + \"\\t\\t\\t$('#favor_trigger').parent('div').removeClass('ask_collect').addClass('ask_collected');\\n\"\n            + \"\\t\\t\\t$('#favor_trigger').attr('title','取消收藏');\\n\"\n            + \"\\t\\t\\t$('#p_favor_count').html(html);\\n\"\n            + \"\\t\\t\\t$('#favor_trigger').unbind('click');\\n\"\n            + \"\\t\\t\\t$('#favor_trigger').click(function(){delete_favor(107307,2);});\\n\"\n            + \"\\t\\t\\t$('#favor_form').html(favor_ok);\\n\"\n            + \"\\t\\t\\tsetTimeout(\\\"close_favor()\\\",3000);\\n\"\n            + \"\\t\\t}\\n\"\n            + \"\\t});\\n\"\n            + \"\\t$(\\\"#TagsSwitcher\\\").one(\\\"click\\\",function(){\\n\"\n            + \"\\t\\t//加载标签数据\\n\"\n            + \"\\t\\t$(\\\"#MyTags\\\").load('/action/ajax/get_my_tags');\\n\"\n            + \"       \\t$(\\\"#MyTags\\\").toggle();\\n\"\n            + \"\\t\\t$(this).html(\\\"收起标签↑\\\");\\n\"\n            + \"        $(this).attr(\\\"state\\\",'on');\\n\"\n            + \"        $(this).click(function(){\\n\"\n            + \"        \\t$(\\\"#MyTags\\\").toggle();\\n\"\n            + \"        \\tvar state = $(this).attr(\\\"state\\\");\\n\"\n            + \"        \\tif(state=='off'){\\n\"\n            + \"        \\t\\t$(this).html(\\\"收起标签↑\\\");\\n\"\n            + \"        \\t\\t$(this).attr(\\\"state\\\",'on');\\n\"\n            + \"        \\t}else{\\n\"\n            + \"        \\t\\t$(this).html(\\\"选取标签↓\\\");\\n\"\n            + \"        \\t\\t$(this).attr(\\\"state\\\",'off');\\n\"\n            + \"        \\t}\\n\"\n            + \"        });\\n\"\n            + \"\\t});\\n\"\n            + \"\\n\"\n            + \"}\\n\"\n            + \"function close_favor(elem_id){\\n\"\n            + \"    $('#favor_trigger').poshytip('destroy');\\n\"\n            + \"}\\n\"\n            + \"function setTag(tv){\\n\"\n            + \"\\tvar t = $(\\\"._favor_input\\\");\\n\"\n            + \"\\tvar value = t.val();\\n\"\n            + \"\\tif(value!=\\\"\\\")\\n\"\n            + \"\\t\\tt.val(value+\\\",\\\"+tv);\\n\"\n            + \"\\telse\\n\"\n            + \"\\t\\tt.val(tv)\\n\"\n            + \"}\\n\"\n            + \"//-->\\n\"\n            + \"</script><div class='clear'></div></div>\\n\"\n            + \"\\t<div id=\\\"OSC_Footer\\\" class='CenterDiv'><style>\\n\"\n            + \".oscapp {text-align:left; width:220px;}\\n\"\n            + \".oscapp span {float:left;width:140px;}\\n\"\n            + \".oscapp a {float:left;text-indent:-9999em;width:16px;margin-left:8px;}\\n\"\n            + \".oscapp a.android {background:url('/img/android.gif') no-repeat left center;}\\n\"\n            + \".oscapp a.iphone {background:url('/img/iphone.gif') no-repeat left center;}\\n\"\n            + \".oscapp a.wp7 {background:url('/img/wp7.gif') no-repeat left center;}\\n\"\n            + \"</style>\\n\"\n            + \"<table width='100%'><tr>\\n\"\n            + \"<td align='left'>&copy; 开源中国(OsChina.NET) | <a href=\\\"http://www.oschina.net/home/about\\\">关于我们</a> | <a href=\\\"mailto:oschina.net@gmail.com\\\">广告联系</a> | <a href=\\\"http://weibo.com/oschina2010\\\" target=\\\"_blank\\\">@新浪微博</a> | <a href=\\\"http://m.oschina.net/\\\">开源中国手机版</a> | <a href='http://www.miitbeian.gov.cn/' target='_blank' style='color:#737573;text-decoration:none;'>粤ICP备12009483号-3</a></td>\\n\"\n            + \"<td class='oscapp'>\\n\" + \"\\t<span>开源中国手机客户端：</span>\\n\"\n            + \"\\t<a href=\\\"http://www.oschina.net/app\\\" class='android' title='Android客户端'>Android</a>\\n\"\n            + \"\\t<a href=\\\"http://www.oschina.net/app\\\" class='iphone' title='iPhone 客户端'>iPhone</a>\\n\"\n            + \"\\t<a href=\\\"http://www.oschina.net/app\\\" class='wp7' title='Windows Phone 客户端'>WP7</a>\\n\" + \"</td>\\n\"\n            + \"</tr>\\n\" + \"</table>\\n\" + \"<script type='text/javascript'>\\n\" + \"<!--\\n\"\n            + \"if (top.location != self.location)top.location=self.location;\\n\" + \"//-->\\n\" + \"</script></div>\\n\"\n            + \"</div>\\n\" + \"</body>\\n\" + \"</html>\\n\"\n            + \"<!-- Generated by OsChina.NET (init:1[ms],page:43[ms],ip:60.55.11.77) -->\";\n\n    @Test\n    public void test() {\n        String text = \"\\n\"\n                + \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\\n\"\n                + \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" xml:lang=\\\"zh-CN\\\" dir=\\\"ltr\\\">\\n\"\n                + \"  <head>\\n\"\n                + \"    <meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"/>\\n\"\n                + \"    <title>jsoup 解析页面商品信息 -  - ITeye技术网站</title>\\n\"\n                + \"    <meta name=\\\"description\\\" content=\\\"今天用了jsoup 解析页面商品信息，感觉比用xpath获取信息准确多了     下面就记录一下：  一、首先去 http://jsoup.org/download 下载jsoup的jar包。     二、下面记录下相关代码：              Document doc = Jsoup.connect(url).get();    //将htm转换成Document类型数据结构        ...\\\" />\\n\"\n                + \"    <meta name=\\\"keywords\\\" content=\\\" jsoup 解析页面商品信息\\\" />\\n\"\n                + \"    <link rel=\\\"shortcut icon\\\" href=\\\"/images/favicon.ico\\\" type=\\\"image/x-icon\\\" />\\n\"\n                + \"    <link rel=\\\"search\\\" type=\\\"application/opensearchdescription+xml\\\" href=\\\"/open_search.xml\\\" title=\\\"ITeye\\\" />\\n\"\n                + \"    <link href=\\\"/rss\\\" rel=\\\"alternate\\\" title=\\\"\\\" type=\\\"application/rss+xml\\\" />\\n\"\n                + \"    <link href=\\\"http://www.iteye.com/stylesheets/blog.css?1365750118\\\" media=\\\"screen\\\" rel=\\\"stylesheet\\\" type=\\\"text/css\\\" />\\n\"\n                + \"<link href=\\\"http://www.iteye.com/stylesheets/themes/blog/blue.css?1326191326\\\" media=\\\"screen\\\" rel=\\\"stylesheet\\\" type=\\\"text/css\\\" />\\n\"\n                + \"    <script src=\\\"http://www.iteye.com/javascripts/application.js?1358214518\\\" type=\\\"text/javascript\\\"></script>\\n\"\n                + \"    <script type=\\\"text/javascript\\\">\\n\"\n                + \"\\n\"\n                + \"  var _gaq = _gaq || [];\\n\"\n                + \"  _gaq.push(['_setAccount', 'UA-535605-1']);\\n\"\n                + \"  _gaq.push(['_setDomainName', 'iteye.com']);\\n\"\n                + \"  _gaq.push(['_trackPageview']);\\n\"\n                + \"\\n\"\n                + \"  (function() {\\n\"\n                + \"    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;\\n\"\n                + \"    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';\\n\"\n                + \"    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);\\n\"\n                + \"  })();\\n\"\n                + \"\\n\"\n                + \"</script>\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"      <link href=\\\"http://www.iteye.com/javascripts/syntaxhighlighter/SyntaxHighlighter.css?1348819953\\\" media=\\\"screen\\\" rel=\\\"stylesheet\\\" type=\\\"text/css\\\" />\\n\"\n                + \"  <script src=\\\"http://www.iteye.com/javascripts/syntaxhighlighter/shCoreCommon.js?1325907333\\\" type=\\\"text/javascript\\\"></script>\\n\"\n                + \"<script src=\\\"http://www.iteye.com/javascripts/hotkey.js?1324994303\\\" type=\\\"text/javascript\\\"></script>\\n\"\n                + \"  <script src=\\\"http://www.iteye.com/javascripts/code_favorites.js?1358214518\\\" type=\\\"text/javascript\\\"></script>\\n\"\n                + \"<script src=\\\"http://www.iteye.com/javascripts/weiboshare.js?1324994303\\\" type=\\\"text/javascript\\\"></script>\\n\"\n                + \"  <link href=\\\"http://www.iteye.com/javascripts/editor/css/ui.css?1324994303\\\" media=\\\"screen\\\" rel=\\\"stylesheet\\\" type=\\\"text/css\\\" />\\n\"\n                + \"  <script src=\\\"http://www.iteye.com/javascripts/editor/compress.js?1358129160\\\" type=\\\"text/javascript\\\"></script>\\n\"\n                + \"  </head>\\n\"\n                + \"  <body>\\n\"\n                + \"    <div id=\\\"header\\\">\\n\"\n                + \"      <div id=\\\"blog_site_nav\\\">\\n\"\n                + \"  <a href=\\\"http://www.iteye.com/\\\" class=\\\"homepage\\\">首页</a>\\n\"\n                + \"  <a href=\\\"http://www.iteye.com/news\\\">资讯</a>\\n\"\n                + \"  <a href=\\\"http://www.iteye.com/magazines\\\">精华</a>\\n\"\n                + \"  <a href=\\\"http://www.iteye.com/forums\\\">论坛</a>\\n\"\n                + \"  <a href=\\\"http://www.iteye.com/ask\\\">问答</a>\\n\"\n                + \"  <a href=\\\"http://www.iteye.com/blogs\\\">博客</a>\\n\"\n                + \"  <a href=\\\"http://www.iteye.com/blogs/subjects\\\">专栏</a>\\n\"\n                + \"  <a href=\\\"http://www.iteye.com/groups\\\">群组</a>\\n\"\n                + \"  <a href=\\\"#\\\" onclick=\\\"return false;\\\" id=\\\"msna\\\"><u>更多</u> <small>▼</small></a>\\n\"\n                + \"  <div class=\\\"quick_menu\\\" style=\\\"display:none;\\\">\\n\"\n                + \"    <a target=\\\"_blank\\\" href=\\\"http://job.iteye.com/iteye\\\">招聘</a>\\n\"\n                + \"    <a href=\\\"http://www.iteye.com/search\\\">搜索</a>\\n\"\n                + \"  </div>\\n\"\n                + \"</div>\\n\"\n                + \"\\n\"\n                + \"      <div id=\\\"user_nav\\\">\\n\"\n                + \"  \\n\"\n                + \"        <a href=\\\"http://flashsword20.iteye.com\\\" title=\\\"查看我的博客首页\\\" class=\\\"welcome\\\">欢迎flashsword20</a>\\n\"\n                + \"    <a id=\\\"notifications_count\\\" href=\\\"http://my.iteye.com/notifications\\\">0</a>\\n\"\n                + \"    \\n\"\n                + \"      <a href=\\\"http://my.iteye.com/messages\\\" title=\\\"你有新的站内短信\\\"><img alt=\\\"Newpm\\\" src=\\\"http://www.iteye.com/images/newpm.gif?1324994303\\\" />收件箱(3)</a>\\n\"\n                + \"    \\n\"\n                + \"    <a href=\\\"http://my.iteye.com\\\" title=\\\"我的应用首页\\\">我的应用</a>\\n\"\n                + \"    <div class=\\\"quick_menu\\\" style=\\\"display:none;\\\">\\n\"\n                + \"      <a href=\\\"http://my.iteye.com/feed\\\" title=\\\"我关注的好友动态消息\\\">我的关注</a>\\n\"\n                + \"      <a href=\\\"http://my.iteye.com/mygroup\\\" title=\\\"我加入的群组最新话题\\\">我的群组</a>\\n\"\n                + \"      <a href=\\\"http://my.iteye.com/myresume\\\" title=\\\"我的个人简历\\\">我的简历</a>\\n\"\n                + \"      <a href=\\\"http://my.iteye.com/admin/album\\\" title=\\\"我的个人简历\\\">我的相册</a>\\n\"\n                + \"      <a href=\\\"http://my.iteye.com/admin/link\\\" title=\\\"我收藏的网络资源链接\\\">我的收藏</a>\\n\"\n                + \"      <a href=\\\"http://my.iteye.com/admin/code\\\" title=\\\"我收藏的代码\\\">我的代码</a>\\n\"\n                + \"      <a href=\\\"http://my.iteye.com/admin/weibo\\\" title=\\\"用微博发表简短的话题\\\">我的微博</a>\\n\"\n                + \"    </div>\\n\"\n                + \"    <a href=\\\"http://flashsword20.iteye.com/admin\\\" title=\\\"管理我的博客\\\">我的博客</a>\\n\"\n                + \"    <a href=\\\"http://my.iteye.com/profile\\\" title=\\\"修改我的个人设置\\\">设置</a>\\n\"\n                + \"    <a href=\\\"/logout\\\" class=\\\"nobg\\\" onclick=\\\"var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'put'); f.appendChild(m);var s = document.createElement('input'); s.setAttribute('type', 'hidden'); s.setAttribute('name', 'authenticity_token'); s.setAttribute('value', 'TDmn/IsWi1Aj4CXKfdMKZZzALz6jbRU/Biw0/QHnsVw='); f.appendChild(s);f.submit();return false;\\\">退出</a>\\n\"\n                + \"  </div>\\n\"\n                + \"\\n\"\n                + \"    </div>\\n\"\n                + \"\\n\"\n                + \"    <div id=\\\"page\\\">\\n\"\n                + \"      <div id=\\\"branding\\\" class=\\\"clearfix\\\">\\n\"\n                + \"        <div id=\\\"blog_name\\\">\\n\"\n                + \"          <h1><a href=\\\"/\\\">masong1987</a></h1>\\n\"\n                + \"        </div>\\n\"\n                + \"        <div id='fd'></div>\\n\"\n                + \"        <div id=\\\"blog_navbar\\\">\\n\"\n                + \"          <ul>\\n\"\n                + \"            <li class='blog_navbar_for'><a href=\\\"http://masong1987.iteye.com\\\"><strong>博客</strong></a></li>\\n\"\n                + \"            <li ><a href=\\\"/weibo\\\">微博</a></li>\\n\"\n                + \"            <li ><a href=\\\"/album\\\">相册</a></li>\\n\"\n                + \"            <li ><a href=\\\"/link\\\">收藏</a></li>\\n\"\n                + \"            <li ><a href=\\\"/blog/guest_book\\\">留言</a></li>\\n\"\n                + \"            <li ><a href=\\\"/blog/profile\\\">关于我</a></li>\\n\"\n                + \"          </ul>\\n\"\n                + \"    \\n\"\n                + \"          <div class=\\\"search\\\">\\n\"\n                + \"            <form action=\\\"/blog/search\\\" method=\\\"get\\\">\\n\"\n                + \"              <input class=\\\"search_text\\\" id=\\\"query\\\" name=\\\"query\\\" style=\\\"margin-left: 10px;width: 110px;\\\" type=\\\"text\\\" value=\\\"\\\" />\\n\"\n                + \"              <input class=\\\"submit_search\\\" type=\\\"submit\\\" value=\\\"\\\" />\\n\"\n                + \"            </form>\\n\"\n                + \"          </div> \\n\"\n                + \"          <div id=\\\"fd\\\"></div>         \\n\"\n                + \"        </div>\\n\"\n                + \"      </div>\\n\"\n                + \"      \\n\"\n                + \"      <div id=\\\"content\\\" class=\\\"clearfix\\\">\\n\"\n                + \"        <div id=\\\"main\\\">\\n\"\n                + \"          \\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"          \\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"<div class=\\\"blog_main\\\">\\n\"\n                + \"  <div class=\\\"blog_title\\\">\\n\"\n                + \"    <h3>\\n\"\n                + \"      <a href=\\\"/blog/1191016\\\">jsoup 解析页面商品信息</a>\\n\"\n                + \"      <em class=\\\"actions\\\">      </em>\\n\"\n                + \"    </h3>\\n\"\n                + \"    <ul class='blog_categories'><strong>博客分类：</strong> <li><a href=\\\"/category/182324\\\">爬虫</a></li> </ul>\\n\"\n                + \"        <div class='news_tag'>&nbsp;</div>\\n\"\n                + \"  </div>\\n\"\n                + \"\\n\"\n                + \"  <div id=\\\"blog_content\\\" class=\\\"blog_content\\\">\\n\"\n                + \"    <p>今天用了jsoup 解析页面商品信息，感觉比用xpath获取信息准确多了</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>下面就记录一下：</p>\\n\"\n                + \"<p>一、首先去 <a href=\\\"http://jsoup.org/download\\\">http://jsoup.org/download</a> 下载jsoup的jar包。</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>二、下面记录下相关代码：</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>      Document doc = Jsoup.connect(url).get();    //将htm转换成Document类型数据结构</p>\\n\"\n                + \"<p> <br>      doc.select(\\\"div:has(div) div#spec-n1:has(img) img\\\").first().attr(\\\"src\\\"));    //查找div下含有div的标签</p>\\n\"\n                + \"<p>      </p>\\n\"\n                + \"<p>      并且 div的id='spec-n1'，此div第一个img标签，img里属性是src的值。</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>      doc.select(\\\"div:has(div) div.crumb:has(a) a:eq(4)\\\").text();    //查找class='crumb'的div下第4个a标签</p>\\n\"\n                + \"<p>      下的值。</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>      doc.select(\\\"div:has(div) div#name:has(h1)\\\").text();     //查找id='name'的div下的h1标签的值。</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>      doc.select(\\\"tbody:has(tr) td.tdTitle:contains(品牌) + td\\\").text();     //查找class='tdTitle'的td标签里</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>      含有‘品牌’td的下一个td标签中内容。</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>      doc.select(\\\"script[type=text/javascript]:not([src~=[a-zA-Z0-9./\\\\\\\\s]+)\\\");     //查找含有此&lt;script </p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>      type=\\\"text/javascript\\\"&gt;……&lt;/script&gt;内容，不含有script标签中有src属性的script，如：</p>\\n\"\n                + \"<p> </p>\\n\"\n                + \"<p>      &lt;script src=\\\"url\\\" type=\\\"text/javascript\\\"&gt;&lt;/script&gt;。</p>\\n\"\n                + \"  </div>\\n\"\n                + \"\\n\"\n                + \"  \\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"  <IFRAME SRC=\\\"/iframe_ggbd/794\\\" SCROLLING=no WIDTH=468 HEIGHT=60 FRAMEBORDER=0></IFRAME>\\n\"\n                + \"  \\n\"\n                + \"  <div id=\\\"bottoms\\\" class=\\\"clearfix\\\">\\n\"\n                + \"    \\n\"\n                + \"    <div id=\\\"share_weibo\\\">分享到：\\n\"\n                + \"      <a data-type='sina' href=\\\"javascript:;\\\" title=\\\"分享到新浪微博\\\"><img src=\\\"/images/sina.jpg\\\"></a>\\n\"\n                + \"      <a data-type='qq' href=\\\"javascript:;\\\" title=\\\"分享到腾讯微博\\\"><img src=\\\"/images/tec.jpg\\\"></a>\\n\"\n                + \"    </div>\\n\"\n                + \"  </div>\\n\"\n                + \"\\n\"\n                + \"  <div class=\\\"blog_nav\\\">\\n\"\n                + \"    <div class=\\\"pre_next\\\">\\n\"\n                + \"      <a href=\\\"/blog/1310327\\\" class=\\\"next\\\" title=\\\"ibatis中书写SQL语句时使用in遇到的问题\\\">ibatis中书写SQL语句时使用in遇到的问题</a>\\n\"\n                + \"      |\\n\"\n                + \"      <a href=\\\"/blog/1189699\\\" class=\\\"pre\\\" title=\\\"尚未备份数据库 &quot;***&quot; 的日志尾部。如果该日志包含您不希望丢失的工作，请使用 BACKUP LOG WITH NORECOVERY 备份该日志。请使用 RE\\\">尚未备份数据库 &quot;***&quot; 的日志尾部。如果该 ...</a>\\n\"\n                + \"    </div>\\n\"\n                + \"  </div>\\n\"\n                + \"  <div class=\\\"blog_bottom\\\">\\n\"\n                + \"    <ul>\\n\"\n                + \"      <li>2011-10-12 18:52</li>\\n\"\n                + \"      <li>浏览 692</li>\\n\"\n                + \"      <li><a href=\\\"#comments\\\">评论(0)</a></li>\\n\"\n                + \"      \\n\"\n                + \"      \\n\"\n                + \"        <li><a href='/admin/link?user_favorite%5Btitle%5D=jsoup+%E8%A7%A3%E6%9E%90%E9%A1%B5%E9%9D%A2%E5%95%86%E5%93%81%E4%BF%A1%E6%81%AF&amp;user_favorite%5Burl%5D=http%3A%2F%2Fmasong1987.iteye.com%2Fblog%2F1191016' target='_blank' class='favorite' onclick=\\\"$$('.favorite_form_spinner')[0].show();new Ajax.Request('/admin/link/new_xhr?user_favorite%5Btitle%5D=jsoup+%E8%A7%A3%E6%9E%90%E9%A1%B5%E9%9D%A2%E5%95%86%E5%93%81%E4%BF%A1%E6%81%AF&amp;user_favorite%5Burl%5D=http%3A%2F%2Fmasong1987.iteye.com%2Fblog%2F1191016', {method: 'get', onSuccess: function(response){$(document.getElementsByTagName('body')[0]).insert({bottom:response.responseText});$$('.favorite_form_spinner')[0].hide();}});return false;\\\">收藏</a><img alt=\\\"Spinner\\\" class=\\\"favorite_form_spinner\\\" src=\\\"http://www.iteye.com/images/spinner.gif?1324994303\\\" style=\\\"vertical-align:bottom;margin-left:7px;display:none;\\\" /></li>\\n\"\n                + \"      \\n\"\n                + \"      <li>分类:<a href=\\\"http://www.iteye.com/blogs/category/opensource\\\">开源软件</a></li>      \\n\"\n                + \"      <li class='last'><a href=\\\"http://www.iteye.com/wiki/blog/1191016\\\" target=\\\"_blank\\\" class=\\\"more\\\">相关推荐</a></li>\\n\"\n                + \"    </ul>\\n\"\n                + \"  </div>\\n\"\n                + \"\\n\"\n                + \"  <div class=\\\"blog_comment\\\">\\n\"\n                + \"    <h5>评论</h5>\\n\"\n                + \"    <a id=\\\"comments\\\" name=\\\"comments\\\"></a>\\n\"\n                + \"    \\n\"\n                + \"    \\n\"\n                + \"    \\n\"\n                + \"  </div>\\n\"\n                + \"\\n\"\n                + \"  <div class=\\\"blog_comment\\\">\\n\"\n                + \"    <h5>发表评论</h5>\\n\"\n                + \"            <form action=\\\"/blog/1191016\\\" id=\\\"comment_form\\\" method=\\\"post\\\" onsubmit=\\\"return false;\\\"><div style=\\\"margin:0;padding:0;display:inline\\\"><input name=\\\"authenticity_token\\\" type=\\\"hidden\\\" value=\\\"TDmn/IsWi1Aj4CXKfdMKZZzALz6jbRU/Biw0/QHnsVw=\\\" /></div>          \\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"  <input type=\\\"hidden\\\" id=\\\"editor_bbcode_flag\\\"/>\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"<div id=\\\"editor_main\\\"><textarea class=\\\"validate-richeditor bad-words min-length-5\\\" cols=\\\"40\\\" id=\\\"editor_body\\\" name=\\\"comment[body]\\\" rows=\\\"20\\\" style=\\\"width: 500px; height: 350px;\\\"></textarea></div>\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"<script type=\\\"text/javascript\\\">\\n\"\n                + \"  var editor = new Control.TextArea.Editor(\\\"editor_body\\\", \\\"bbcode\\\", false);\\n\"\n                + \"</script>\\n\"\n                + \"\\n\"\n                + \"          <p style=\\\"text-align:right;margin-right:30px;\\\">(快捷键 Alt+S / Ctrl+Enter) <input class=\\\"submit\\\" id=\\\"quick_reply_button\\\" name=\\\"commit\\\" type=\\\"submit\\\" value=\\\"提交\\\" /></p>\\n\"\n                + \"       </form>\\n\"\n                + \"        <script type=\\\"text/javascript\\\">\\n\"\n                + \"          new HotKey(\\\"s\\\",function() {$('quick_reply_button').click();},{altKey: true, ctrlKey: false});\\n\"\n                + \"          new HotKey(new Number(13),function() {$('quick_reply_button').click();},{altKey: false, ctrlKey: true});\\n\"\n                + \"\\n\"\n                + \"          new Validation(\\\"comment_form\\\", {immediate: false, onFormValidate: function(result, form){\\n\"\n                + \"            if(result) {\\n\"\n                + \"              new Ajax.Request('/blog/create_comment/1191016', {\\n\"\n                + \"                onFailure:function(response){\\n\"\n                + \"                  $('comments').insert({after:response.responseText})\\n\"\n                + \"                  form.spinner.hide();\\n\"\n                + \"                  Element.scrollTo($('comments'));\\n\"\n                + \"                },\\n\"\n                + \"                onSuccess:function(response){\\n\"\n                + \"                  Element.scrollTo($('comments'));\\n\"\n                + \"                  var new_comment = new Element('div', {}).update(response.responseText).firstChild;\\n\"\n                + \"                  var comment_id = new_comment.readAttribute('id');\\n\"\n                + \"\\n\"\n                + \"                  $('comments').insert({after:response.responseText});\\n\"\n                + \"                  $('editor_body').value = \\\"\\\";\\n\"\n                + \"\\n\"\n                + \"                  var css_rules = '#' + comment_id + ' pre';\\n\"\n                + \"                  highlightNewAddContent(css_rules);\\n\"\n                + \"                  processComment();\\n\"\n                + \"                  code_favorites_init(css_rules);\\n\"\n                + \"                  \\n\"\n                + \"                  form.spinner.hide();\\n\"\n                + \"                }, parameters:Form.serialize(form)\\n\"\n                + \"              });\\n\"\n                + \"            }\\n\"\n                + \"        }});\\n\"\n                + \"        </script>\\n\"\n                + \"        </div>\\n\"\n                + \"</div>\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"<script type=\\\"text/javascript\\\">\\n\"\n                + \"  dp.SyntaxHighlighter.HighlightAll('code', true, true);\\n\"\n                + \"\\n\"\n                + \"  $$('#main .blog_content pre[name=code]').each(function(pre, index){ // blog content\\n\"\n                + \"    var post_id = 1191016;\\n\"\n                + \"    var location = window.location;\\n\"\n                + \"    source_url = location.protocol + \\\"//\\\" + location.host + location.pathname + location.search;\\n\"\n                + \"    pre.writeAttribute('codeable_id', post_id);\\n\"\n                + \"    pre.writeAttribute('codeable_type', \\\"OschinaBlog\\\");\\n\"\n                + \"    pre.writeAttribute('source_url', source_url);\\n\"\n                + \"    pre.writeAttribute('pre_index', index);\\n\"\n                + \"    pre.writeAttribute('title', 'jsoup 解析页面商品信息');\\n\"\n                + \"  });\\n\"\n                + \"\\n\"\n                + \"  fix_image_size($$('div.blog_content img'), 700);\\n\"\n                + \"\\n\"\n                + \"  function processComment() {\\n\"\n                + \"    $$('#main .blog_comment > div').each(function(comment){// comment\\n\"\n                + \"      var post_id = comment.id.substr(2);\\n\"\n                + \"      $$(\\\"#\\\"+comment.id+\\\" pre[name=code]\\\").each(function(pre, index){\\n\"\n                + \"        var location = window.location;\\n\"\n                + \"        source_url = location.protocol + \\\"//\\\" + location.host + location.pathname + location.search;\\n\"\n                + \"        source_url += \\\"#\\\" + comment.id;\\n\"\n                + \"        pre.writeAttribute('codeable_id', post_id);\\n\"\n                + \"        pre.writeAttribute('codeable_type', \\\"BlogComment\\\");\\n\"\n                + \"        pre.writeAttribute('source_url', source_url);\\n\"\n                + \"        pre.writeAttribute('pre_index', index);\\n\"\n                + \"        pre.writeAttribute('title', 'jsoup 解析页面商品信息');\\n\"\n                + \"      });\\n\"\n                + \"    });\\n\"\n                + \"  }\\n\"\n                + \"\\n\"\n                + \"  function quote_comment(id) {\\n\"\n                + \"    new Ajax.Request('/editor/quote', {\\n\"\n                + \"      parameters: {'id':id, 'type':'BlogComment'},\\n\"\n                + \"      onSuccess:function(response){editor.bbcode_editor.textarea.insertAfterSelection(response.responseText);\\n\"\n                + \"        Element.scrollTo(editor.bbcode_editor.textarea.element);}\\n\"\n                + \"    });\\n\"\n                + \"  }\\n\"\n                + \"\\n\"\n                + \"  code_favorites_init();\\n\"\n                + \"  processComment();\\n\"\n                + \"  new WeiboShare({share_buttons: $('share_weibo'), img_scope: $('blog_content')});\\n\"\n                + \"</script>\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"\\n\"\n                + \"        </div>\\n\"\n                + \"\\n\"\n                + \"        <div id=\\\"local\\\">\\n\"\n                + \"          <div class=\\\"local_top\\\"></div>\\n\"\n                + \"          <div id=\\\"blog_owner\\\">\\n\"\n                + \"  <div id=\\\"blog_owner_logo\\\"><a href='http://masong1987.iteye.com'><img alt=\\\"masong1987的博客\\\" class=\\\"logo\\\" src=\\\"http://www.iteye.com/images/user-logo.gif?1324994303\\\" title=\\\"masong1987的博客: \\\" /></a></div>\\n\"\n                + \"  <div id=\\\"blog_owner_name\\\">masong1987</div>\\n\"\n                + \"</div>\\n\"\n                + \"\\n\"\n                + \"          <div id=\\\"blog_actions\\\">\\n\"\n                + \"            <ul>\\n\"\n                + \"              <li>浏览: 5401 次</li>\\n\"\n                + \"              <li>性别: <img alt=\\\"Icon_minigender_1\\\" src=\\\"http://www.iteye.com/images/icon_minigender_1.gif?1324994303\\\" title=\\\"男\\\" /></li>\\n\"\n                + \"              <li>来自: 北京</li>\\n\"\n                + \"              <li><img src='/images/status/offline.gif'/></li>\\n\"\n                + \"              \\n\"\n                + \"                <li>\\n\"\n                + \"                  <a href=\\\"http://my.iteye.com/messages/new?message%5Breceiver_name%5D=masong1987\\\" class=\\\"message\\\" title=\\\"发送站内短信\\\">发短消息</a>\\n\"\n                + \"                  \\n\"\n                + \"                    <a href=\\\"http://my.iteye.com/feed?subscription%5Bsubscribed_user_name%5D=masong1987\\\" class=\\\"subscription\\\" onclick=\\\"var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var s = document.createElement('input'); s.setAttribute('type', 'hidden'); s.setAttribute('name', 'authenticity_token'); s.setAttribute('value', 'TDmn/IsWi1Aj4CXKfdMKZZzALz6jbRU/Biw0/QHnsVw='); f.appendChild(s);f.submit();return false;\\\">关注</a>\\n\"\n                + \"                  \\n\"\n                + \"                </li>\\n\"\n                + \"              \\n\"\n                + \"            </ul>\\n\"\n                + \"          </div>\\n\"\n                + \"          <div id=\\\"user_visits\\\" class=\\\"clearfix\\\">\\n\"\n                + \"            <h5>最近访客 <span style='font-weight:normal;font-size:12px;padding-left:30px;'><a href=\\\"/blog/user_visits\\\">更多访客&gt;&gt;</a></span></h5>\\n\"\n                + \"            \\n\"\n                + \"              <div class=\\\"user_visit\\\">\\n\"\n                + \"                <div class=\\\"logo\\\"><a href='http://flashsword20.iteye.com' target='_blank'><img alt=\\\"flashsword20的博客\\\" class=\\\"logo\\\" src=\\\"http://www.iteye.com/images/user-logo-thumb.gif?1324994303\\\" title=\\\"flashsword20的博客: \\\" /></a></div>\\n\"\n                + \"                <div class=\\\"left\\\"><a href='http://flashsword20.iteye.com' target='_blank' title='flashsword20'>flashsword20</a></div>\\n\"\n                + \"              </div>\\n\"\n                + \"            \\n\"\n                + \"              <div class=\\\"user_visit\\\">\\n\"\n                + \"                <div class=\\\"logo\\\"><a href='http://dylinshi126.iteye.com' target='_blank'><img alt=\\\"dylinshi126的博客\\\" class=\\\"logo\\\" src=\\\"http://www.iteye.com/images/user-logo-thumb.gif?1324994303\\\" title=\\\"dylinshi126的博客: \\\" /></a></div>\\n\"\n                + \"                <div class=\\\"left\\\"><a href='http://dylinshi126.iteye.com' target='_blank' title='dylinshi126'>dylinshi126</a></div>\\n\"\n                + \"              </div>\\n\"\n                + \"            \\n\"\n                + \"              <div class=\\\"user_visit\\\">\\n\"\n                + \"                <div class=\\\"logo\\\"><a href='http://machoo.iteye.com' target='_blank'><img alt=\\\"machoo的博客\\\" class=\\\"logo\\\" src=\\\"http://www.iteye.com/upload/logo/user/590501/f3e5a6de-fa04-3ca9-92bd-378230b128c8-thumb.jpg?1321544632\\\" title=\\\"machoo的博客: 虚拟机终结者\\\" /></a></div>\\n\"\n                + \"                <div class=\\\"left\\\"><a href='http://machoo.iteye.com' target='_blank' title='machoo'>machoo</a></div>\\n\"\n                + \"              </div>\\n\"\n                + \"            \\n\"\n                + \"              <div class=\\\"user_visit\\\">\\n\"\n                + \"                <div class=\\\"logo\\\"><a href='http://arson.iteye.com' target='_blank'><img alt=\\\"arson的博客\\\" class=\\\"logo\\\" src=\\\"http://www.iteye.com/upload/logo/user/511499/91eafa67-ebbb-32d2-a1c4-fc1c169b5c66-thumb.jpg?1310020715\\\" title=\\\"arson的博客: \\\" /></a></div>\\n\"\n                + \"                <div class=\\\"left\\\"><a href='http://arson.iteye.com' target='_blank' title='arson'>arson</a></div>\\n\"\n                + \"              </div>\\n\"\n                + \"            \\n\"\n                + \"          </div>\\n\"\n                + \"\\n\"\n                + \"          \\n\"\n                + \"\\n\"\n                + \"                      <div id=\\\"blog_menu\\\">\\n\"\n                + \"              <h5>文章分类</h5>\\n\"\n                + \"              <ul>\\n\"\n                + \"                <li><a href=\\\"/\\\">全部博客 (10)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/180178\\\">java (1)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/178810\\\">JavaScript (2)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/181978\\\">SQLServer (1)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/214133\\\">MySQL (1)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/182324\\\">爬虫 (1)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/195652\\\">ibatis (2)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/195881\\\">Spring (1)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/216639\\\">tomcat (0)</a></li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/category/217595\\\">数据结构 (1)</a></li>\\n\"\n                + \"                \\n\"\n                + \"              </ul>\\n\"\n                + \"            </div>\\n\"\n                + \"            <div id='month_blogs'>\\n\"\n                + \"              <h5>社区版块</h5>\\n\"\n                + \"              <ul>\\n\"\n                + \"                <li><a href=\\\"/blog/news\\\">我的资讯</a> (0)</li>\\n\"\n                + \"                <li>\\n\"\n                + \"                  <a href=\\\"/blog/post\\\">我的论坛</a> (0)\\n\"\n                + \"                </li>\\n\"\n                + \"                <li><a href=\\\"/blog/answered_problems\\\">我的问答</a> (0)</li>\\n\"\n                + \"              </ul>\\n\"\n                + \"            </div>\\n\"\n                + \"            <div id=\\\"month_blogs\\\">\\n\"\n                + \"              <h5>存档分类</h5>\\n\"\n                + \"              <ul>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/blog/monthblog/2012-04\\\">2012-04</a> (2)</li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/blog/monthblog/2012-03\\\">2012-03</a> (1)</li>\\n\"\n                + \"                \\n\"\n                + \"                  <li><a href=\\\"/blog/monthblog/2012-02\\\">2012-02</a> (1)</li>\\n\"\n                + \"                \\n\"\n                + \"                <li><a href=\\\"/blog/monthblog_more\\\">更多存档...</a></li>\\n\"\n                + \"              </ul>\\n\"\n                + \"            </div>\\n\"\n                + \"            \\n\"\n                + \"            \\n\"\n                + \"\\n\"\n                + \"            <div id=\\\"guest_books\\\">\\n\"\n                + \"              <h5>最新评论</h5>\\n\"\n                + \"              <ul>\\n\"\n                + \"                \\n\"\n                + \"                <li>\\n\"\n                + \"                  <a href='http://marrymyy.iteye.com' target='_blank' title='marrymyy'>marrymyy</a>： \\n\"\n                + \"                  太好了，刚遇到这个问题，有用<br />\\n\"\n                + \"                  <a href=\\\"/blog/1189699#bc2305339\\\">尚未备份数据库 &quot;***&quot; 的日志尾部。如果该日志包含您不希望丢失的工作，请使用 BACKUP LOG WITH NORECOVERY 备份该日志。请使用 RE</a>\\n\"\n                + \"                </li>\\n\"\n                + \"                \\n\"\n                + \"              </ul>\\n\"\n                + \"            </div>\\n\"\n                + \"\\n\"\n                + \"            <div class=\\\"local_bottom\\\"></div>\\n\"\n                + \"          \\n\"\n                + \"        </div>\\n\"\n                + \"      </div>\\n\"\n                + \"\\n\"\n                + \"      <div id=\\\"footer\\\" class=\\\"clearfix\\\">\\n\"\n                + \"        <div id=\\\"copyright\\\">\\n\"\n                + \"          <hr/>\\n\"\n                + \"          声明：ITeye文章版权属于作者，受法律保护。没有作者书面许可不得转载。若作者同意转载，必须以超链接形式标明文章原始出处和作者。<br />\\n\"\n                + \"          &copy; 2003-2012 ITeye.com.   All rights reserved.  [ 京ICP证110151号  京公网安备110105010620 ]\\n\"\n                + \"        </div>\\n\"\n                + \"      </div>\\n\"\n                + \"    </div>\\n\"\n                + \"    <script type=\\\"text/javascript\\\">\\n\"\n                + \"  document.write(\\\"<img src='http://stat.iteye.com/?url=\\\"+ encodeURIComponent(document.location.href) + \\\"&referrer=\\\" + encodeURIComponent(document.referrer) + \\\"&user_id=635408' width='0' height='0' />\\\");\\n\"\n                + \"</script>\\n\" + \"\\n\" + \"    \\n\" + \"    \\n\" + \"  </body>\\n\" + \"</html>\\n\";\n        String text2 = \"<div>aaa</div>\";\n        XpathSelector xpathSelector = new XpathSelector(\n                \"//div[@id='main']/div[@class='blog_main']/div[@class='blog_title']/h3/a/text()\");\n        String select = xpathSelector.select(text);\n        Assert.assertEquals(\"jsoup 解析页面商品信息\", select);\n    }\n\n    @Test\n    public void testOschina() {\n        Html html1 = new Html(html);\n        Assert.assertEquals(\"再次吐槽easyui\", html1.xpath(\"//*[@class='QTitle']/h1/a/text()\").toString());\n        Assert.assertNotNull(html1.$(\"a[href]\").xpath(\"//@href\").all());\n        Selectors.xpath(\"/abc/\").select(\"\");\n    }\n\n    @Test\n    public void testXPath2() {\n        String text = \"<h1>眉山：扎实推进农业农村工作 促农持续增收<br>\\n\" +\n                \"<span>2013-07-31 23:29:45&nbsp;&nbsp;&nbsp;来源：<a href=\\\"http://www.mshw.net\\\" target=\\\"_blank\\\" style=\\\"color:#AAA\\\">眉山网</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;责任编辑：张斯炜</span></h1>\";\n        Xpath2Selector xpathSelector = new Xpath2Selector(\"//h1/text()\");\n        Assert.assertEquals(\"眉山：扎实推进农业农村工作 促农持续增收\", xpathSelector.select(text));\n    }\n\n    @Test\n    public void testXpath2Selector() {\n        Xpath2Selector xpath2Selector = new Xpath2Selector(\"//xhtml:a/@href\");\n        String select = xpath2Selector.select(html);\n        Assert.assertEquals(\"http://www.oschina.net/\", select);\n\n        List<String> selectList = xpath2Selector.selectList(html);\n        Assert.assertEquals(113, selectList.size());\n        Assert.assertEquals(\"http://www.oschina.net/\", selectList.get(0));\n    }\n\n    @Ignore(\"take long time\")\n    @Test\n    public void performanceTest() {\n        Xpath2Selector xpath2Selector = new Xpath2Selector(\"//a\");\n        long time = System.currentTimeMillis();\n        for (int i = 0; i < 1000; i++) {\n            xpath2Selector.selectList(html);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        XpathSelector xpathSelector = new XpathSelector(\"//a\");\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 1000; i++) {\n            xpathSelector.selectList(html);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 1000; i++) {\n            xpath2Selector.selectList(html);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        CssSelector cssSelector = new CssSelector(\"a\");\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 1000; i++) {\n            cssSelector.selectList(html);\n        }\n        System.out.println(\"css \" + (System.currentTimeMillis() - time));\n    }\n\n    @Ignore(\"take long time\")\n    @Test\n    public void parserPerformanceTest() throws XPatherException {\n        System.out.println(html.length());\n\n        HtmlCleaner htmlCleaner = new HtmlCleaner();\n        TagNode tagNode = htmlCleaner.clean(html);\n        Document document = Jsoup.parse(html);\n\n        long time = System.currentTimeMillis();\n        for (int i = 0; i < 2000; i++) {\n            htmlCleaner.clean(html);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 2000; i++) {\n            tagNode.evaluateXPath(\"//a\");\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        System.out.println(\"=============\");\n\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 2000; i++) {\n            Jsoup.parse(html);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 2000; i++) {\n            document.select(\"a\");\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        System.out.println(\"=============\");\n\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 2000; i++) {\n            htmlCleaner.clean(html);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 2000; i++) {\n            tagNode.evaluateXPath(\"//a\");\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n        System.out.println(\"=============\");\n\n        XPathEvaluator compile = Xsoup.compile(\"//a\");\n        time = System.currentTimeMillis();\n        for (int i = 0; i < 2000; i++) {\n            compile.evaluate(document);\n        }\n        System.out.println(System.currentTimeMillis() - time);\n\n    }\n\n    /**\n     * New api test\n     *\n     * @author hooy\n     * @since 8.0\n     */\n    private String rank = \"<div class=\\\"container\\\"><div class=\\\"container-bd\\\"><div class=\\\"c-left\\\"><div class=\\\"periods\\\"><a class=\\\"active\\\" href=\\\"http://www.ruoxia.com/top/dianji/day\\\">日</a> <a href=\\\"http://www.ruoxia.com/top/dianji/week\\\">周</a> <a href=\\\"http://www.ruoxia.com/top/dianji/month\\\">月</a></div><h1 class=\\\"page-title\\\"><i class=\\\"icon icon-rank\\\"></i> <span class=\\\"rankTitle\\\">点击榜</span></h1><div data-collect-id=\\\"2550\\\" class=\\\"mod mod-clean pattern-update-list update-list\\\"><div class=\\\"bd\\\"><table><thead><tr><th width=\\\"30\\\">排名</th><th width=\\\"50\\\">分类</th><th>书名/最新章节</th><th width=\\\"60\\\">作者</th><th width=\\\"80\\\">推荐</th><th width=\\\"100\\\">更新时间</th></tr></thead><tbody><tr><td class=\\\"index\\\">1.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=54\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">现实</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"1\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/83981\\\" targe=\\\"_blank\\\" class=\\\"name\\\">校园妖孽高手</a> <a href=\\\"http://www.ruoxia.com/book/83981/2154682\\\" class=\\\"chapter\\\">第三十章 求你收我为徒</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/17562559\\\" targe=\\\"_blank\\\" class=\\\"author\\\">白色风帆</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">11-24 22:32</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">2.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"2\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/43462\\\" targe=\\\"_blank\\\" class=\\\"name\\\">凤谋图</a> <a href=\\\"http://www.ruoxia.com/book/43462/1141799\\\" class=\\\"chapter\\\">写给最亲爱的你们（完结感言+新书推荐）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4482112\\\" targe=\\\"_blank\\\" class=\\\"author\\\">斑陆离</a></div></td><td><div>1047</div></td><td><span class=\\\"time\\\">03-04 14:44</span></td></tr><tr><td class=\\\"index\\\">3.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=54\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">现实</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"3\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/90878\\\" targe=\\\"_blank\\\" class=\\\"name\\\">女神的全职高手</a> <a href=\\\"http://www.ruoxia.com/book/90878/2491923\\\" class=\\\"chapter\\\">第五十一章 大结局。</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/18515896\\\" targe=\\\"_blank\\\" class=\\\"author\\\">白玉书生</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">07-20 09:06</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">4.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"4\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/80166\\\" targe=\\\"_blank\\\" class=\\\"name\\\">闪婚夺爱：总裁老公太霸道</a> <a href=\\\"http://www.ruoxia.com/book/80166/2167580\\\" class=\\\"chapter\\\">第140章 大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5152286\\\" targe=\\\"_blank\\\" class=\\\"author\\\">丛慕然</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">12-03 09:12</span></td></tr><tr><td class=\\\"index\\\">5.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=54\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">现实</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"5\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/84765\\\" targe=\\\"_blank\\\" class=\\\"name\\\">最强神眼</a> <a href=\\\"http://www.ruoxia.com/book/84765/2261859\\\" class=\\\"chapter\\\">第72章 没羞没臊（大结局）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3822610\\\" targe=\\\"_blank\\\" class=\\\"author\\\">枫长弦</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">02-01 21:12</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">6.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"6\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/41149\\\" targe=\\\"_blank\\\" class=\\\"name\\\">胎楼</a> <a href=\\\"http://www.ruoxia.com/book/41149/1135302\\\" class=\\\"chapter\\\">写在最后的私话</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5095127\\\" targe=\\\"_blank\\\" class=\\\"author\\\">丫丫雅雅</a></div></td><td><div>3455</div></td><td><span class=\\\"time\\\">02-28 12:31</span></td></tr><tr><td class=\\\"index\\\">7.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"7\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/40863\\\" targe=\\\"_blank\\\" class=\\\"name\\\">我的未婚夫白狐大人</a> <a href=\\\"http://www.ruoxia.com/book/40863/1180864\\\" class=\\\"chapter\\\">【免费公告】新书《道士房东，快开门》已经发布了</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5058618\\\" targe=\\\"_blank\\\" class=\\\"author\\\">佚之狐</a></div></td><td><div>20614</div></td><td><span class=\\\"time\\\">03-31 12:37</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">8.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=50\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">复仇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"8\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/30816\\\" targe=\\\"_blank\\\" class=\\\"name\\\">艳骨</a> <a href=\\\"http://www.ruoxia.com/book/30816/769427\\\" class=\\\"chapter\\\">番外（司浔）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1729575\\\" targe=\\\"_blank\\\" class=\\\"author\\\">e小调</a></div></td><td><div>55</div></td><td><span class=\\\"time\\\">06-03 11:43</span></td></tr><tr><td class=\\\"index\\\">9.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"9\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/82131\\\" targe=\\\"_blank\\\" class=\\\"name\\\">王爷，别动粗</a> <a href=\\\"http://www.ruoxia.com/book/82131/2097402\\\" class=\\\"chapter\\\">第041章 看来这个女人是在乎他的</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/15972439\\\" targe=\\\"_blank\\\" class=\\\"author\\\">春亦盎然</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">10-27 18:50</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">10.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=19\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宫斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"10\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/52279\\\" targe=\\\"_blank\\\" class=\\\"name\\\">深宫胭脂乱</a> <a href=\\\"http://www.ruoxia.com/book/52279/1567850\\\" class=\\\"chapter\\\">【263】为夫来的，可还算及时 HE版，感谢小伙伴们大半年来的不离不弃！</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1664188\\\" targe=\\\"_blank\\\" class=\\\"author\\\">糖小贩</a></div></td><td><div>320</div></td><td><span class=\\\"time\\\">10-31 13:58</span></td></tr><tr><td class=\\\"index\\\">11.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=19\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宫斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"11\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/45621\\\" targe=\\\"_blank\\\" class=\\\"name\\\">深宫安容传</a> <a href=\\\"http://www.ruoxia.com/book/45621/1361138\\\" class=\\\"chapter\\\">番外4 韶华不负，生生世世 （有红包哟）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5146663\\\" targe=\\\"_blank\\\" class=\\\"author\\\">鱼墨</a></div></td><td><div>6268</div></td><td><span class=\\\"time\\\">07-12 20:23</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">12.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=54\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">现实</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"12\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/69000\\\" targe=\\\"_blank\\\" class=\\\"name\\\">我们的爱，未完待续</a> <a href=\\\"http://www.ruoxia.com/book/69000/1680336\\\" class=\\\"chapter\\\">第175章 番外：人生没有太晚的开始</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5152286\\\" targe=\\\"_blank\\\" class=\\\"author\\\">丛慕然</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">01-18 23:00</span></td></tr><tr><td class=\\\"index\\\">13.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"13\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/65082\\\" targe=\\\"_blank\\\" class=\\\"name\\\">我在时光深处忘记你</a> <a href=\\\"http://www.ruoxia.com/book/65082/1631623\\\" class=\\\"chapter\\\">新书已开</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5222423\\\" targe=\\\"_blank\\\" class=\\\"author\\\">柯三岁</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">12-14 20:50</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">14.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=22\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">修真</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"14\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/85911\\\" targe=\\\"_blank\\\" class=\\\"name\\\">女神总裁的妖孽兵王</a> <a href=\\\"http://www.ruoxia.com/book/85911/2265301\\\" class=\\\"chapter\\\">第52章 好吃的？</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5507678\\\" targe=\\\"_blank\\\" class=\\\"author\\\">包仙人</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">02-03 23:40</span></td></tr><tr><td class=\\\"index\\\">15.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"15\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/83315\\\" targe=\\\"_blank\\\" class=\\\"name\\\">攻心为上：薄情总裁求放过</a> <a href=\\\"http://www.ruoxia.com/book/83315/2126598\\\" class=\\\"chapter\\\">第21章：别让我讨厌你</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/17393569\\\" targe=\\\"_blank\\\" class=\\\"author\\\">七月晚笙</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">11-06 23:38</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">16.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"16\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/39361\\\" targe=\\\"_blank\\\" class=\\\"name\\\">妙手萌妃</a> <a href=\\\"http://www.ruoxia.com/book/39361/1021373\\\" class=\\\"chapter\\\">九十六</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4048268\\\" targe=\\\"_blank\\\" class=\\\"author\\\">糖酱不甩</a></div></td><td><div>191</div></td><td><span class=\\\"time\\\">12-02 23:37</span></td></tr><tr><td class=\\\"index\\\">17.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"17\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/35111\\\" targe=\\\"_blank\\\" class=\\\"name\\\">炮灰女配二嫁攻略</a> <a href=\\\"http://www.ruoxia.com/book/35111/942024\\\" class=\\\"chapter\\\">番外</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4165342\\\" targe=\\\"_blank\\\" class=\\\"author\\\">烬相思</a></div></td><td><div>412</div></td><td><span class=\\\"time\\\">10-13 22:39</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">18.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"18\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/46464\\\" targe=\\\"_blank\\\" class=\\\"name\\\">娇妻别逃</a> <a href=\\\"http://www.ruoxia.com/book/46464/1344044\\\" class=\\\"chapter\\\">系列文开更了！</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5625273\\\" targe=\\\"_blank\\\" class=\\\"author\\\">九竹</a></div></td><td><div>635</div></td><td><span class=\\\"time\\\">07-01 13:15</span></td></tr><tr><td class=\\\"index\\\">19.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"19\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/26318\\\" targe=\\\"_blank\\\" class=\\\"name\\\">妾惊华</a> <a href=\\\"http://www.ruoxia.com/book/26318/789360\\\" class=\\\"chapter\\\">番外 3 正式完结 鬼月篇</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2970776\\\" targe=\\\"_blank\\\" class=\\\"author\\\">温柔的小白兔</a></div></td><td><div>144</div></td><td><span class=\\\"time\\\">06-18 09:35</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">20.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=20\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宅斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"20\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/46115\\\" targe=\\\"_blank\\\" class=\\\"name\\\">废妾青瑶</a> <a href=\\\"http://www.ruoxia.com/book/46115/1420123\\\" class=\\\"chapter\\\">冷玉•世上堪哀只有痴 （不无聊依旧发着玩）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3428612\\\" targe=\\\"_blank\\\" class=\\\"author\\\">梦中说梦</a></div></td><td><div>1032</div></td><td><span class=\\\"time\\\">08-15 19:03</span></td></tr><tr><td class=\\\"index\\\">21.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=19\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宫斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"21\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/78428\\\" targe=\\\"_blank\\\" class=\\\"name\\\">帝女长乐</a> <a href=\\\"http://www.ruoxia.com/book/78428/2057572\\\" class=\\\"chapter\\\">第一百六十三章大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5672126\\\" targe=\\\"_blank\\\" class=\\\"author\\\">梅花香雨</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">09-30 20:32</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">22.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"22\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/72124\\\" targe=\\\"_blank\\\" class=\\\"name\\\">总裁在上，萌妻不乖</a> <a href=\\\"http://www.ruoxia.com/book/72124/1880164\\\" class=\\\"chapter\\\">新文公告</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5152286\\\" targe=\\\"_blank\\\" class=\\\"author\\\">丛慕然</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">06-05 11:31</span></td></tr><tr><td class=\\\"index\\\">23.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=47\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">重生</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"23\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/63141\\\" targe=\\\"_blank\\\" class=\\\"name\\\">重生之只想好好爱你</a> <a href=\\\"http://www.ruoxia.com/book/63141/1604556\\\" class=\\\"chapter\\\">第三百三十一章 明白心意</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2149297\\\" targe=\\\"_blank\\\" class=\\\"author\\\">忘忧草</a></div></td><td><div>80</div></td><td><span class=\\\"time\\\">11-25 19:56</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">24.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=49\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">异世</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"24\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/45001\\\" targe=\\\"_blank\\\" class=\\\"name\\\">末世之活下去</a> <a href=\\\"http://www.ruoxia.com/book/45001/1073956\\\" class=\\\"chapter\\\">第一百一十四章　大结局之另种结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4876856\\\" targe=\\\"_blank\\\" class=\\\"author\\\">清风随意</a></div></td><td><div>68</div></td><td><span class=\\\"time\\\">01-12 10:06</span></td></tr><tr><td class=\\\"index\\\">25.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"25\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/70099\\\" targe=\\\"_blank\\\" class=\\\"name\\\">以婚试爱：总裁老公太过分</a> <a href=\\\"http://www.ruoxia.com/book/70099/1871138\\\" class=\\\"chapter\\\">第二百章 温哥华的暖冬</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/14023478\\\" targe=\\\"_blank\\\" class=\\\"author\\\">乔慕燃</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">05-29 18:46</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">26.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"26\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/51499\\\" targe=\\\"_blank\\\" class=\\\"name\\\">顾瑾，我们要好好的</a> <a href=\\\"http://www.ruoxia.com/book/51499/2122738\\\" class=\\\"chapter\\\">新书~婚不谈爱，总裁老公住隔壁</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2099877\\\" targe=\\\"_blank\\\" class=\\\"author\\\">一朵66</a></div></td><td><div>2778</div></td><td><span class=\\\"time\\\">11-04 17:48</span></td></tr><tr><td class=\\\"index\\\">27.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=1\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"27\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/62293\\\" targe=\\\"_blank\\\" class=\\\"name\\\">吟尸调妃</a> <a href=\\\"http://www.ruoxia.com/book/62293/1620092\\\" class=\\\"chapter\\\">作品最后</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/6052301\\\" targe=\\\"_blank\\\" class=\\\"author\\\">乔沫若轩</a></div></td><td><div>207</div></td><td><span class=\\\"time\\\">12-06 16:57</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">28.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"28\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/32090\\\" targe=\\\"_blank\\\" class=\\\"name\\\">嫡女皇商</a> <a href=\\\"http://www.ruoxia.com/book/32090/1065024\\\" class=\\\"chapter\\\">皇商开始修文</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2595590\\\" targe=\\\"_blank\\\" class=\\\"author\\\">十七帝</a></div></td><td><div>260</div></td><td><span class=\\\"time\\\">01-04 23:26</span></td></tr><tr><td class=\\\"index\\\">29.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"29\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/80386\\\" targe=\\\"_blank\\\" class=\\\"name\\\">豪门养女：总裁请息怒</a> <a href=\\\"http://www.ruoxia.com/book/80386/2174667\\\" class=\\\"chapter\\\">第一百三十五章 最终番外</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2079910\\\" targe=\\\"_blank\\\" class=\\\"author\\\">杨家小呆</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">12-07 21:39</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">30.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"30\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/46774\\\" targe=\\\"_blank\\\" class=\\\"name\\\">这盛世，如你所愿</a> <a href=\\\"http://www.ruoxia.com/book/46774/1282874\\\" class=\\\"chapter\\\">【免费公告】完结感言+新书公告</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4950247\\\" targe=\\\"_blank\\\" class=\\\"author\\\">南风知意</a></div></td><td><div>1127</div></td><td><span class=\\\"time\\\">06-06 17:28</span></td></tr><tr><td class=\\\"index\\\">31.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"31\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/34887\\\" targe=\\\"_blank\\\" class=\\\"name\\\">江湖有晴天</a> <a href=\\\"http://www.ruoxia.com/book/34887/901292\\\" class=\\\"chapter\\\">第一二零章 阴谋叠加，尘埃落定（结局篇）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2077970\\\" targe=\\\"_blank\\\" class=\\\"author\\\">洛紫晴</a></div></td><td><div>113</div></td><td><span class=\\\"time\\\">09-13 09:06</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">32.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"32\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/57202\\\" targe=\\\"_blank\\\" class=\\\"name\\\">江山不若美人顾</a> <a href=\\\"http://www.ruoxia.com/book/57202/1711384\\\" class=\\\"chapter\\\">番外：最终章</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2067719\\\" targe=\\\"_blank\\\" class=\\\"author\\\">慕容以泽</a></div></td><td><div>597</div></td><td><span class=\\\"time\\\">02-14 18:47</span></td></tr><tr><td class=\\\"index\\\">33.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"33\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/54323\\\" targe=\\\"_blank\\\" class=\\\"name\\\">一碰冥婚</a> <a href=\\\"http://www.ruoxia.com/book/54323/1280258\\\" class=\\\"chapter\\\">第一百三十九章 秦无极诡上身</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4782768\\\" targe=\\\"_blank\\\" class=\\\"author\\\">二手玫瑰</a></div></td><td><div>528</div></td><td><span class=\\\"time\\\">06-04 22:04</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">34.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"34\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/27977\\\" targe=\\\"_blank\\\" class=\\\"name\\\">侯门丑媳</a> <a href=\\\"http://www.ruoxia.com/book/27977/774342\\\" class=\\\"chapter\\\">第202章 大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/666311\\\" targe=\\\"_blank\\\" class=\\\"author\\\">东方怡然</a></div></td><td><div>328</div></td><td><span class=\\\"time\\\">06-06 22:09</span></td></tr><tr><td class=\\\"index\\\">35.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"35\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/26287\\\" targe=\\\"_blank\\\" class=\\\"name\\\">将本红妆</a> <a href=\\\"http://www.ruoxia.com/book/26287/756383\\\" class=\\\"chapter\\\">完结感言 我们新文见</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2382839\\\" targe=\\\"_blank\\\" class=\\\"author\\\">故城阿九</a></div></td><td><div>539</div></td><td><span class=\\\"time\\\">05-24 14:42</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">36.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"36\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/79251\\\" targe=\\\"_blank\\\" class=\\\"name\\\">妃卿非故：世子，有事好商量</a> <a href=\\\"http://www.ruoxia.com/book/79251/2304572\\\" class=\\\"chapter\\\">最后的一点小内容</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5270097\\\" targe=\\\"_blank\\\" class=\\\"author\\\">白玉琼楼</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">03-05 23:27</span></td></tr><tr><td class=\\\"index\\\">37.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"37\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/31901\\\" targe=\\\"_blank\\\" class=\\\"name\\\">美人潋滟</a> <a href=\\\"http://www.ruoxia.com/book/31901/1998239\\\" class=\\\"chapter\\\">新文《爱有余温，触手可及》</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1412126\\\" targe=\\\"_blank\\\" class=\\\"author\\\">冷在</a></div></td><td><div>3215</div></td><td><span class=\\\"time\\\">08-21 16:38</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">38.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=19\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宫斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"38\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/52159\\\" targe=\\\"_blank\\\" class=\\\"name\\\">后宫长梧传</a> <a href=\\\"http://www.ruoxia.com/book/52159/1402619\\\" class=\\\"chapter\\\">160 大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/6660633\\\" targe=\\\"_blank\\\" class=\\\"author\\\">嘟嘟爱吃鱼</a></div></td><td><div>905</div></td><td><span class=\\\"time\\\">08-04 20:24</span></td></tr><tr><td class=\\\"index\\\">39.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"39\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/51408\\\" targe=\\\"_blank\\\" class=\\\"name\\\">冥夫别过来</a> <a href=\\\"http://www.ruoxia.com/book/51408/1380765\\\" class=\\\"chapter\\\">新文速递（我好慌）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2538685\\\" targe=\\\"_blank\\\" class=\\\"author\\\">陌妖</a></div></td><td><div>1328</div></td><td><span class=\\\"time\\\">07-25 10:58</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">40.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"40\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/44908\\\" targe=\\\"_blank\\\" class=\\\"name\\\">公子好倾城</a> <a href=\\\"http://www.ruoxia.com/book/44908/1094556\\\" class=\\\"chapter\\\">番外</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5193966\\\" targe=\\\"_blank\\\" class=\\\"author\\\">颜箴言</a></div></td><td><div>203</div></td><td><span class=\\\"time\\\">01-27 20:53</span></td></tr><tr><td class=\\\"index\\\">41.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=19\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宫斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"41\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/33791\\\" targe=\\\"_blank\\\" class=\\\"name\\\">女相倾天下</a> <a href=\\\"http://www.ruoxia.com/book/33791/879406\\\" class=\\\"chapter\\\">第九十三章 大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3970105\\\" targe=\\\"_blank\\\" class=\\\"author\\\">睡梦之城</a></div></td><td><div>407</div></td><td><span class=\\\"time\\\">08-31 09:03</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">42.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=20\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宅斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"42\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/28208\\\" targe=\\\"_blank\\\" class=\\\"name\\\">庶辜</a> <a href=\\\"http://www.ruoxia.com/book/28208/727884\\\" class=\\\"chapter\\\">请假通知</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1434153\\\" targe=\\\"_blank\\\" class=\\\"author\\\">君醉</a></div></td><td><div>16</div></td><td><span class=\\\"time\\\">05-03 17:38</span></td></tr><tr><td class=\\\"index\\\">43.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"43\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/79201\\\" targe=\\\"_blank\\\" class=\\\"name\\\">隐婚蜜爱：首席老公别太坏</a> <a href=\\\"http://www.ruoxia.com/book/79201/2130842\\\" class=\\\"chapter\\\">第138章 筱雅，我终于等到你了！</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/15011710\\\" targe=\\\"_blank\\\" class=\\\"author\\\">古月初雪</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">11-10 08:00</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">44.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"44\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/76667\\\" targe=\\\"_blank\\\" class=\\\"name\\\">予你爱情，还我光阴</a> <a href=\\\"http://www.ruoxia.com/book/76667/1937700\\\" class=\\\"chapter\\\">第066章 结尾</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/15544088\\\" targe=\\\"_blank\\\" class=\\\"author\\\">美人折</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">07-12 21:37</span></td></tr><tr><td class=\\\"index\\\">45.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"45\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/73761\\\" targe=\\\"_blank\\\" class=\\\"name\\\">王妃，王爷又来拆墙了</a> <a href=\\\"http://www.ruoxia.com/book/73761/1908547\\\" class=\\\"chapter\\\">有红包</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5672126\\\" targe=\\\"_blank\\\" class=\\\"author\\\">梅花香雨</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">06-23 21:02</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">46.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"46\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/42459\\\" targe=\\\"_blank\\\" class=\\\"name\\\">爱上我的阴阳先生</a> <a href=\\\"http://www.ruoxia.com/book/42459/1273498\\\" class=\\\"chapter\\\">完本感言</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3227798\\\" targe=\\\"_blank\\\" class=\\\"author\\\">魑魅魁魃</a></div></td><td><div>1382</div></td><td><span class=\\\"time\\\">05-31 20:36</span></td></tr><tr><td class=\\\"index\\\">47.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=47\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">重生</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"47\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/29035\\\" targe=\\\"_blank\\\" class=\\\"name\\\">嫡女重生</a> <a href=\\\"http://www.ruoxia.com/book/29035/828174\\\" class=\\\"chapter\\\">完结感言</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3252807\\\" targe=\\\"_blank\\\" class=\\\"author\\\">小喵家的温婉</a></div></td><td><div>334</div></td><td><span class=\\\"time\\\">07-16 19:19</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">48.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"48\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/58092\\\" targe=\\\"_blank\\\" class=\\\"name\\\">亲爱的，我不等你了</a> <a href=\\\"http://www.ruoxia.com/book/58092/2105021\\\" class=\\\"chapter\\\">嘿，还有宝贝在吗？</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5209133\\\" targe=\\\"_blank\\\" class=\\\"author\\\">依人茶</a></div></td><td><div>505</div></td><td><span class=\\\"time\\\">11-01 16:42</span></td></tr><tr><td class=\\\"index\\\">49.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"49\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/77198\\\" targe=\\\"_blank\\\" class=\\\"name\\\">爱已过期：总裁前夫请放手</a> <a href=\\\"http://www.ruoxia.com/book/77198/2085300\\\" class=\\\"chapter\\\">第一百六十七章 余生有你陪伴 全文完</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2149297\\\" targe=\\\"_blank\\\" class=\\\"author\\\">忘忧草</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">10-19 18:32</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">50.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"50\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/56098\\\" targe=\\\"_blank\\\" class=\\\"name\\\">你的甜蜜，触手不及</a> <a href=\\\"http://www.ruoxia.com/book/56098/1476223\\\" class=\\\"chapter\\\">圆宝的完结感言</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/7144700\\\" targe=\\\"_blank\\\" class=\\\"author\\\">圆八宝</a></div></td><td><div>540</div></td><td><span class=\\\"time\\\">09-19 19:18</span></td></tr><tr><td class=\\\"index\\\">51.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"51\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/45650\\\" targe=\\\"_blank\\\" class=\\\"name\\\">江太太，恋爱已生效</a> <a href=\\\"http://www.ruoxia.com/book/45650/1161174\\\" class=\\\"chapter\\\">江先生和江太太的恋城旧忆（三） 怀孕篇</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5548454\\\" targe=\\\"_blank\\\" class=\\\"author\\\">呆小萌的包子</a></div></td><td><div>226</div></td><td><span class=\\\"time\\\">03-18 13:09</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">52.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"52\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/28004\\\" targe=\\\"_blank\\\" class=\\\"name\\\">倾城不过未亡人</a> <a href=\\\"http://www.ruoxia.com/book/28004/1147278\\\" class=\\\"chapter\\\">楚木萧萧 第一章</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2067719\\\" targe=\\\"_blank\\\" class=\\\"author\\\">慕容以泽</a></div></td><td><div>1026</div></td><td><span class=\\\"time\\\">03-08 16:28</span></td></tr><tr><td class=\\\"index\\\">53.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=47\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">重生</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"53\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/46534\\\" targe=\\\"_blank\\\" class=\\\"name\\\">重生女二嫁攻略</a> <a href=\\\"http://www.ruoxia.com/book/46534/1122895\\\" class=\\\"chapter\\\">算是完结感言吧</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2595590\\\" targe=\\\"_blank\\\" class=\\\"author\\\">十七帝</a></div></td><td><div>304</div></td><td><span class=\\\"time\\\">02-19 10:25</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">54.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"54\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/42151\\\" targe=\\\"_blank\\\" class=\\\"name\\\">冥婚难测</a> <a href=\\\"http://www.ruoxia.com/book/42151/1117992\\\" class=\\\"chapter\\\">完结感言（有惊喜哦，戳进来看看）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1494532\\\" targe=\\\"_blank\\\" class=\\\"author\\\">鬼爹</a></div></td><td><div>2617</div></td><td><span class=\\\"time\\\">02-15 20:57</span></td></tr><tr><td class=\\\"index\\\">55.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"55\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/33600\\\" targe=\\\"_blank\\\" class=\\\"name\\\">傻王贤妃</a> <a href=\\\"http://www.ruoxia.com/book/33600/891372\\\" class=\\\"chapter\\\">感言</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1249730\\\" targe=\\\"_blank\\\" class=\\\"author\\\">汐凉</a></div></td><td><div>199</div></td><td><span class=\\\"time\\\">09-04 19:43</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">56.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=16\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">同人</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"56\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/30689\\\" targe=\\\"_blank\\\" class=\\\"name\\\">清宫瑾妃传</a> <a href=\\\"http://www.ruoxia.com/book/30689/832178\\\" class=\\\"chapter\\\">第220章 此生唯你（大结局）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1677848\\\" targe=\\\"_blank\\\" class=\\\"author\\\">芳小苓</a></div></td><td><div>768</div></td><td><span class=\\\"time\\\">07-19 20:00</span></td></tr><tr><td class=\\\"index\\\">57.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=20\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宅斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"57\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/83309\\\" targe=\\\"_blank\\\" class=\\\"name\\\">庶女世子妃</a> <a href=\\\"http://www.ruoxia.com/book/83309/2273884\\\" class=\\\"chapter\\\">第一六五章 完美结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3227798\\\" targe=\\\"_blank\\\" class=\\\"author\\\">魑魅魁魃</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">02-13 18:13</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">58.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"58\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/80185\\\" targe=\\\"_blank\\\" class=\\\"name\\\">我曾卑微爱过你</a> <a href=\\\"http://www.ruoxia.com/book/80185/2135467\\\" class=\\\"chapter\\\">第96章 大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5321782\\\" targe=\\\"_blank\\\" class=\\\"author\\\">懒桔不懒</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">11-12 22:23</span></td></tr><tr><td class=\\\"index\\\">59.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"59\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/74007\\\" targe=\\\"_blank\\\" class=\\\"name\\\">妾倾天下</a> <a href=\\\"http://www.ruoxia.com/book/74007/1962721\\\" class=\\\"chapter\\\">第162章 母仪天下（全书完）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/14890712\\\" targe=\\\"_blank\\\" class=\\\"author\\\">璃璃</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">07-28 23:42</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">60.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"60\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/66546\\\" targe=\\\"_blank\\\" class=\\\"name\\\">纵使情深如故</a> <a href=\\\"http://www.ruoxia.com/book/66546/1697902\\\" class=\\\"chapter\\\">【175】一言不合就完结</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1664188\\\" targe=\\\"_blank\\\" class=\\\"author\\\">糖小贩</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">02-03 23:09</span></td></tr><tr><td class=\\\"index\\\">61.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"61\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/59727\\\" targe=\\\"_blank\\\" class=\\\"name\\\">爱你，可念不可说</a> <a href=\\\"http://www.ruoxia.com/book/59727/1664270\\\" class=\\\"chapter\\\">完结感言</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1494532\\\" targe=\\\"_blank\\\" class=\\\"author\\\">鬼爹</a></div></td><td><div>285</div></td><td><span class=\\\"time\\\">01-07 19:21</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">62.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=47\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">重生</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"62\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/27946\\\" targe=\\\"_blank\\\" class=\\\"name\\\">美人皮，噬骨香</a> <a href=\\\"http://www.ruoxia.com/book/27946/1538607\\\" class=\\\"chapter\\\">【完结感言】</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1311937\\\" targe=\\\"_blank\\\" class=\\\"author\\\">涣茶</a></div></td><td><div>654</div></td><td><span class=\\\"time\\\">10-12 18:16</span></td></tr><tr><td class=\\\"index\\\">63.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=43\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">异能</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"63\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/47521\\\" targe=\\\"_blank\\\" class=\\\"name\\\">末世之拐个系统做男神</a> <a href=\\\"http://www.ruoxia.com/book/47521/1319390\\\" class=\\\"chapter\\\">以后的日子 番外二</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5776194\\\" targe=\\\"_blank\\\" class=\\\"author\\\">TK。优酱</a></div></td><td><div>617</div></td><td><span class=\\\"time\\\">06-18 20:23</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">64.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=19\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宫斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"64\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/50621\\\" targe=\\\"_blank\\\" class=\\\"name\\\">笑嫁江山</a> <a href=\\\"http://www.ruoxia.com/book/50621/1276556\\\" class=\\\"chapter\\\">第三百一十五章 伤怀</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4819693\\\" targe=\\\"_blank\\\" class=\\\"author\\\">与文</a></div></td><td><div>27</div></td><td><span class=\\\"time\\\">06-02 21:05</span></td></tr><tr><td class=\\\"index\\\">65.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=28\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">种田</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"65\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/31900\\\" targe=\\\"_blank\\\" class=\\\"name\\\">重生之带着装备去种田</a> <a href=\\\"http://www.ruoxia.com/book/31900/886594\\\" class=\\\"chapter\\\">第一百三十五章 大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/21456\\\" targe=\\\"_blank\\\" class=\\\"author\\\">灵山小道士</a></div></td><td><div>206</div></td><td><span class=\\\"time\\\">08-31 19:23</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">66.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=20\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宅斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"66\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/27944\\\" targe=\\\"_blank\\\" class=\\\"name\\\">恶毒女配的悠然生活</a> <a href=\\\"http://www.ruoxia.com/book/27944/847971\\\" class=\\\"chapter\\\">第240章 下辈子你还是我的妻(完)</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3147647\\\" targe=\\\"_blank\\\" class=\\\"author\\\">翦语</a></div></td><td><div>2444</div></td><td><span class=\\\"time\\\">08-19 15:51</span></td></tr><tr><td class=\\\"index\\\">67.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=20\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宅斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"67\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/28130\\\" targe=\\\"_blank\\\" class=\\\"name\\\">一品皇妻</a> <a href=\\\"http://www.ruoxia.com/book/28130/857549\\\" class=\\\"chapter\\\">第158章 大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3167696\\\" targe=\\\"_blank\\\" class=\\\"author\\\">七月白椿</a></div></td><td><div>818</div></td><td><span class=\\\"time\\\">08-07 23:38</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">68.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=25\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">现代</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"68\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/95582\\\" targe=\\\"_blank\\\" class=\\\"name\\\">龙拳</a> <a href=\\\"http://www.ruoxia.com/book/95582/10698954\\\" class=\\\"chapter\\\">第一千八百七十章 大结局！ （六千字大章）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/19990474\\\" targe=\\\"_blank\\\" class=\\\"author\\\"></a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">12-23 17:02</span></td></tr><tr><td class=\\\"index\\\">69.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=1\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"69\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/95598\\\" targe=\\\"_blank\\\" class=\\\"name\\\">民调局异闻录之勉传</a> <a href=\\\"http://www.ruoxia.com/book/95598/10384052\\\" class=\\\"chapter\\\">第四百一十七章 归宿</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/19990495\\\" targe=\\\"_blank\\\" class=\\\"author\\\"></a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">07-23 12:00</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">70.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"70\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/69929\\\" targe=\\\"_blank\\\" class=\\\"name\\\">原来爱你那么伤</a> <a href=\\\"http://www.ruoxia.com/book/69929/2105024\\\" class=\\\"chapter\\\">嘿，还有宝贝在吗？</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5209133\\\" targe=\\\"_blank\\\" class=\\\"author\\\">依人茶</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">11-01 16:43</span></td></tr><tr><td class=\\\"index\\\">71.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"71\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/78853\\\" targe=\\\"_blank\\\" class=\\\"name\\\">情深如许：霸道总裁我不约</a> <a href=\\\"http://www.ruoxia.com/book/78853/2029552\\\" class=\\\"chapter\\\">谢谢大家，新书求收藏</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/16083833\\\" targe=\\\"_blank\\\" class=\\\"author\\\">余无晴</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">09-12 00:01</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">72.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"72\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/74481\\\" targe=\\\"_blank\\\" class=\\\"name\\\">戾妃倾城</a> <a href=\\\"http://www.ruoxia.com/book/74481/1823456\\\" class=\\\"chapter\\\">番外1 几经辗转，故人坟草三尺高</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/14543004\\\" targe=\\\"_blank\\\" class=\\\"author\\\">凌家女孩</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">04-27 22:42</span></td></tr><tr><td class=\\\"index\\\">73.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"73\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/71766\\\" targe=\\\"_blank\\\" class=\\\"name\\\">婚久情深：闪婚娇妻深深爱</a> <a href=\\\"http://www.ruoxia.com/book/71766/1809712\\\" class=\\\"chapter\\\">第二十二章 大结局（完）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2079910\\\" targe=\\\"_blank\\\" class=\\\"author\\\">杨家小呆</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">04-19 13:55</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">74.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=43\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">异能</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"74\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/47956\\\" targe=\\\"_blank\\\" class=\\\"name\\\">末世重生之女配归来</a> <a href=\\\"http://www.ruoxia.com/book/47956/1391378\\\" class=\\\"chapter\\\">第98章</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5862910\\\" targe=\\\"_blank\\\" class=\\\"author\\\">慕晓玥</a></div></td><td><div>62</div></td><td><span class=\\\"time\\\">07-30 00:00</span></td></tr><tr><td class=\\\"index\\\">75.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"75\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/49277\\\" targe=\\\"_blank\\\" class=\\\"name\\\">皇帝你被征用了</a> <a href=\\\"http://www.ruoxia.com/book/49277/1373550\\\" class=\\\"chapter\\\">新书准备ing</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/6052301\\\" targe=\\\"_blank\\\" class=\\\"author\\\">乔沫若轩</a></div></td><td><div>1307</div></td><td><span class=\\\"time\\\">07-20 16:41</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">76.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"76\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/52370\\\" targe=\\\"_blank\\\" class=\\\"name\\\">道士房东，快开门</a> <a href=\\\"http://www.ruoxia.com/book/52370/1366506\\\" class=\\\"chapter\\\">第六百七十章 姓什么</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5058618\\\" targe=\\\"_blank\\\" class=\\\"author\\\">佚之狐</a></div></td><td><div>12820</div></td><td><span class=\\\"time\\\">07-15 23:46</span></td></tr><tr><td class=\\\"index\\\">77.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"77\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/43341\\\" targe=\\\"_blank\\\" class=\\\"name\\\">欠你一世长安</a> <a href=\\\"http://www.ruoxia.com/book/43341/1282888\\\" class=\\\"chapter\\\">【免费公告】新书《如果爱情看得见》求支持</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4950247\\\" targe=\\\"_blank\\\" class=\\\"author\\\">南风知意</a></div></td><td><div>828</div></td><td><span class=\\\"time\\\">06-06 17:54</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">78.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=20\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宅斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"78\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/45958\\\" targe=\\\"_blank\\\" class=\\\"name\\\">鬓云香腮雪</a> <a href=\\\"http://www.ruoxia.com/book/45958/1256794\\\" class=\\\"chapter\\\">番外 倾心。（秦相宜&amp;太子）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5575980\\\" targe=\\\"_blank\\\" class=\\\"author\\\">萌七妹</a></div></td><td><div>985</div></td><td><span class=\\\"time\\\">05-20 23:53</span></td></tr><tr><td class=\\\"index\\\">79.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"79\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/39183\\\" targe=\\\"_blank\\\" class=\\\"name\\\">一纸冥婚</a> <a href=\\\"http://www.ruoxia.com/book/39183/1198169\\\" class=\\\"chapter\\\">关于番外+新书</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4782768\\\" targe=\\\"_blank\\\" class=\\\"author\\\">二手玫瑰</a></div></td><td><div>4960</div></td><td><span class=\\\"time\\\">04-12 15:58</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">80.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=24\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"80\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/46118\\\" targe=\\\"_blank\\\" class=\\\"name\\\">阴夫驾到</a> <a href=\\\"http://www.ruoxia.com/book/46118/1140031\\\" class=\\\"chapter\\\">第一六零话 最后的决战（结局篇）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2077970\\\" targe=\\\"_blank\\\" class=\\\"author\\\">洛紫晴</a></div></td><td><div>245</div></td><td><span class=\\\"time\\\">03-02 23:11</span></td></tr><tr><td class=\\\"index\\\">81.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=20\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宅斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"81\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/39812\\\" targe=\\\"_blank\\\" class=\\\"name\\\">美人鬓</a> <a href=\\\"http://www.ruoxia.com/book/39812/1046332\\\" class=\\\"chapter\\\">完本公告</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/4819693\\\" targe=\\\"_blank\\\" class=\\\"author\\\">与文</a></div></td><td><div>34</div></td><td><span class=\\\"time\\\">12-21 10:11</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">82.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=20\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">宅斗</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"82\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/28007\\\" targe=\\\"_blank\\\" class=\\\"name\\\">妻居一品</a> <a href=\\\"http://www.ruoxia.com/book/28007/834007\\\" class=\\\"chapter\\\">第259章 墩仔和淼淼（四）【大结局】</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/3154400\\\" targe=\\\"_blank\\\" class=\\\"author\\\">沙提子</a></div></td><td><div>1411</div></td><td><span class=\\\"time\\\">07-21 00:00</span></td></tr><tr><td class=\\\"index\\\">83.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=25\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">现代</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"83\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/95617\\\" targe=\\\"_blank\\\" class=\\\"name\\\">女总裁的超级高手</a> <a href=\\\"http://www.ruoxia.com/book/95617/10401594\\\" class=\\\"chapter\\\">第3609章、这样挺好！（大结局下）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/19990521\\\" targe=\\\"_blank\\\" class=\\\"author\\\"></a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">07-31 10:10</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">84.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=1\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"84\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/89974\\\" targe=\\\"_blank\\\" class=\\\"name\\\">活见诡</a> <a href=\\\"http://www.ruoxia.com/book/89974/2452074\\\" class=\\\"chapter\\\">第三十六章 威风凌凌</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/19016569\\\" targe=\\\"_blank\\\" class=\\\"author\\\">一骑妃子笑</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">06-18 13:53</span></td></tr><tr><td class=\\\"index\\\">85.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=15\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">架空</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"85\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/78680\\\" targe=\\\"_blank\\\" class=\\\"name\\\">压寨夫君请上轿</a> <a href=\\\"http://www.ruoxia.com/book/78680/2168844\\\" class=\\\"chapter\\\">103 我说了，我已经嫁过人了 大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5724868\\\" targe=\\\"_blank\\\" class=\\\"author\\\">唯水眠心</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">12-03 23:41</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">86.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=1\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">玄奇</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"86\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/83856\\\" targe=\\\"_blank\\\" class=\\\"name\\\">九命</a> <a href=\\\"http://www.ruoxia.com/book/83856/2160919\\\" class=\\\"chapter\\\">第045章 柳家的秘密</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/2547718\\\" targe=\\\"_blank\\\" class=\\\"author\\\">渴雨</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">11-28 22:13</span></td></tr><tr><td class=\\\"index\\\">87.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"87\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/79904\\\" targe=\\\"_blank\\\" class=\\\"name\\\">暖婚契约：腹黑总裁的呆萌妻</a> <a href=\\\"http://www.ruoxia.com/book/79904/2128055\\\" class=\\\"chapter\\\">【118】大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/15919521\\\" targe=\\\"_blank\\\" class=\\\"author\\\">落小妹</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">11-07 22:48</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">88.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"88\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/77693\\\" targe=\\\"_blank\\\" class=\\\"name\\\">眷你如火如荼</a> <a href=\\\"http://www.ruoxia.com/book/77693/2010568\\\" class=\\\"chapter\\\">【终章】、经年后，道一句好久不见</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/6095530\\\" targe=\\\"_blank\\\" class=\\\"author\\\">粟越</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">08-29 23:15</span></td></tr><tr><td class=\\\"index\\\">89.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=28\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">种田</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"89\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/45352\\\" targe=\\\"_blank\\\" class=\\\"name\\\">女儿香满田</a> <a href=\\\"http://www.ruoxia.com/book/45352/1998240\\\" class=\\\"chapter\\\">新文《爱有余温，触手可及》</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/1412126\\\" targe=\\\"_blank\\\" class=\\\"author\\\">冷在</a></div></td><td><div>1831</div></td><td><span class=\\\"time\\\">08-21 16:38</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">90.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"90\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/74771\\\" targe=\\\"_blank\\\" class=\\\"name\\\">千万婚约：求你放了我！</a> <a href=\\\"http://www.ruoxia.com/book/74771/1936113\\\" class=\\\"chapter\\\">【112】</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/12820436\\\" targe=\\\"_blank\\\" class=\\\"author\\\">纳兰一梦</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">07-11 21:25</span></td></tr><tr><td class=\\\"index\\\">91.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"91\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/73591\\\" targe=\\\"_blank\\\" class=\\\"name\\\">虐爱成殇：冷傲总裁太绝情</a> <a href=\\\"http://www.ruoxia.com/book/73591/1892707\\\" class=\\\"chapter\\\">第138章 小玥，我爱你，你愿意嫁给我吗？</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/15011710\\\" targe=\\\"_blank\\\" class=\\\"author\\\">古月初雪</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">06-13 15:37</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">92.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"92\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/71367\\\" targe=\\\"_blank\\\" class=\\\"name\\\">总裁的替身小萌妻</a> <a href=\\\"http://www.ruoxia.com/book/71367/1838552\\\" class=\\\"chapter\\\">178·尾声</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/366099\\\" targe=\\\"_blank\\\" class=\\\"author\\\">沈乔</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">05-07 22:10</span></td></tr><tr><td class=\\\"index\\\">93.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"93\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/71221\\\" targe=\\\"_blank\\\" class=\\\"name\\\">早安，小甜妻</a> <a href=\\\"http://www.ruoxia.com/book/71221/1733121\\\" class=\\\"chapter\\\">第71章：大结局（完）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/14303346\\\" targe=\\\"_blank\\\" class=\\\"author\\\">星辉熠熠</a></div></td><td><div>0</div></td><td><span class=\\\"time\\\">02-28 00:01</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">94.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"94\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/60151\\\" targe=\\\"_blank\\\" class=\\\"name\\\">娇妻休逃</a> <a href=\\\"http://www.ruoxia.com/book/60151/1632790\\\" class=\\\"chapter\\\">第326章 我带你飞（大结局）</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5625273\\\" targe=\\\"_blank\\\" class=\\\"author\\\">九竹</a></div></td><td><div>304</div></td><td><span class=\\\"time\\\">12-16 07:30</span></td></tr><tr><td class=\\\"index\\\">95.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"95\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/54317\\\" targe=\\\"_blank\\\" class=\\\"name\\\">宋先生，我的爱情已过期</a> <a href=\\\"http://www.ruoxia.com/book/54317/1578696\\\" class=\\\"chapter\\\">完结感言+新书速递《给我一场盛宴，纪念你离开》</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/868367\\\" targe=\\\"_blank\\\" class=\\\"author\\\">流年mengo</a></div></td><td><div>669</div></td><td><span class=\\\"time\\\">11-07 18:16</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">96.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=7\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">仙侠</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"96\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/61509\\\" targe=\\\"_blank\\\" class=\\\"name\\\">神君快到碗里来</a> <a href=\\\"http://www.ruoxia.com/book/61509/1512646\\\" class=\\\"chapter\\\">110.最美是回忆</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/8528296\\\" targe=\\\"_blank\\\" class=\\\"author\\\">柒月绯然</a></div></td><td><div>54</div></td><td><span class=\\\"time\\\">09-25 19:51</span></td></tr><tr><td class=\\\"index\\\">97.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=12\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">豪门</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"97\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/58932\\\" targe=\\\"_blank\\\" class=\\\"name\\\">我爱你，与你无关</a> <a href=\\\"http://www.ruoxia.com/book/58932/1444784\\\" class=\\\"chapter\\\">大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/10048716\\\" targe=\\\"_blank\\\" class=\\\"author\\\">南有木木</a></div></td><td><div>655</div></td><td><span class=\\\"time\\\">08-31 13:02</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">98.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=54\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">现实</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"98\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/55800\\\" targe=\\\"_blank\\\" class=\\\"name\\\">你与时光皆薄凉</a> <a href=\\\"http://www.ruoxia.com/book/55800/1340844\\\" class=\\\"chapter\\\">完结感言</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/6517325\\\" targe=\\\"_blank\\\" class=\\\"author\\\">七寸南季</a></div></td><td><div>374</div></td><td><span class=\\\"time\\\">06-29 09:55</span></td></tr><tr><td class=\\\"index\\\">99.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=17\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">穿越</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"99\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/48522\\\" targe=\\\"_blank\\\" class=\\\"name\\\">夫君，你别跑</a> <a href=\\\"http://www.ruoxia.com/book/48522/1320612\\\" class=\\\"chapter\\\">完结了，完结了</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5209133\\\" targe=\\\"_blank\\\" class=\\\"author\\\">依人茶</a></div></td><td><div>373</div></td><td><span class=\\\"time\\\">06-19 18:07</span></td></tr><tr class=\\\"even\\\"><td class=\\\"index\\\">100.</td><td><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/all?sort=35\\\" class=\\\"tag\\\" targe=\\\"_blank\\\">婚恋</a></td><td><div class=\\\"range\\\"><a data-collect-index=\\\"100\\\" target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/book/52762\\\" targe=\\\"_blank\\\" class=\\\"name\\\">彼年豆蔻，白首不离</a> <a href=\\\"http://www.ruoxia.com/book/52762/1280140\\\" class=\\\"chapter\\\">第一百四十二章大结局</a></div></td><td><div class=\\\"range\\\"><a target=\\\"_blank\\\" href=\\\"http://www.ruoxia.com/people/5672126\\\" targe=\\\"_blank\\\" class=\\\"author\\\">梅花香雨</a></div></td><td><div>159</div></td><td><span class=\\\"time\\\">06-04 21:05</span></td></tr></tbody></table></div></div></div><div class=\\\"c-right\\\"><div class=\\\"c-right\\\"><div class=\\\"mod mod-block sidebar-menu\\\"><div class=\\\"hd\\\"><h5><span>排行榜</span></h5></div><div class=\\\"bd\\\"><ul><li id=\\\"monthly\\\"><a href=\\\"http://www.ruoxia.com/top/monthly/day?rank=13\\\">钻石榜</a></li><li id=\\\"dianji\\\" class=\\\"active\\\"><a href=\\\"http://www.ruoxia.com/top/dianji/day\\\">点击榜</a></li><li id=\\\"tuijian\\\"><a href=\\\"http://www.ruoxia.com/top/tuijian/day?rank=3\\\">推荐榜</a></li><li id=\\\"xinshu\\\"><a href=\\\"http://www.ruoxia.com/top/xinshu/day\\\">新书榜</a></li><li id=\\\"pengchang\\\"><a href=\\\"http://www.ruoxia.com/top/pengchang/day\\\">捧场榜</a></li><li id=\\\"finishding\\\"><a href=\\\"http://www.ruoxia.com/top/finishding/day\\\">完本订阅榜</a></li><li id=\\\"wbTuijian\\\"><a href=\\\"http://www.ruoxia.com/top/wbTuijian/day?rank=16\\\">完本推荐榜</a></li><li id=\\\"wbMonthly\\\"><a href=\\\"http://www.ruoxia.com/top/wbMonthly/day?rank=17\\\">完本钻石榜</a></li></ul></div></div></div></div></div></div>\";\n\n    @Test\n    public void testStringAPI() {\n        // testAPI: selectList(String) -> selectList(Node)\n        List<String> items = new Xpath2Selector(\"//div[@class=\\\"bd\\\"]//tbody/tr\").selectList(rank);\n        Assert.assertSame(100, items.size());\n        // testAPI: select(String) -> select(Node)\n        String name = new Xpath2Selector(\"//td[3]/div/a[1]/text()\").select(items.get(10));\n        Assert.assertEquals(\"深宫安容传\", name);\n    }\n\n    @Test\n    public void testNodeAPI() {\n        // testAPI: selectNodes(String) -> selectNodes(Node)\n        List<Node> items = new Xpath2Selector(\"//div[@class=\\\"bd\\\"]//tbody/tr\").selectNodes(rank);\n        Assert.assertSame(100, items.size());\n        // testAPI: selectNode(Node)\n        Node item = new Xpath2Selector(\"./td[3]/div/a[1]\").selectNode(items.get(10));\n        String name = new Xpath2Selector(\"./text()\").select(item);\n        Assert.assertEquals(\"深宫安容传\", name);\n    }\n\n    @Test\n    public void testUtilAPI() throws TransformerException {\n        Node item = Xpath2Selector.newInstance(\"//div[@class=\\\"bd\\\"]//tbody/tr[11]/td[3]/div/a[1]/text()\").selectNode(rank);\n        // testAPI: nodeToString(Node) -> nodesToStrings(List<Node>)\n        String name = JaxpSelectorUtils.nodeToString(item);\n        Assert.assertEquals(\"深宫安容传\", name);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-scripts/README.md",
    "content": "webmagic-scripts\n======\n## 目标：\n使得可以用简单脚本的方式编写爬虫，从而为一些常用场景提供可流通的脚本。如果已经有人写好了脚本，那么你直接使用就可以了！\n\n## 实例:\n例如：我需要抓github的仓库数据，可以这样写一个脚本(javascript)：\n\n```javascript\nvar name=xpath(\"//h1[@class='entry-title public']/strong/a/text()\")\nvar readme=xpath(\"//div[@id='readme']/tidyText()\")\nvar star=xpath(\"//ul[@class='pagehead-actions']/li[1]//a[@class='social-count js-social-count']/text()\")\nvar fork=xpath(\"//ul[@class='pagehead-actions']/li[2]//a[@class='social-count']/text()\")\nvar url=page.getUrl().toString()\nif (name!=null){\n    println(name)\n    println(readme)\n    println(star)\n    println(url)\n}\n\nurls(\"(https://github\\\\.com/\\\\w+/\\\\w+)\")\nurls(\"(https://github\\\\.com/\\\\w+)\")\n```\n\n然后使用webmagic加载并启动它，无需下载依赖、编写代码、执行的过程。目前已经有控制台版本，请下载[http://code4craft.qiniudn.com/webmagic-console.tar.gz](http://code4craft.qiniudn.com/webmagic-console.tar.gz)。\n\n解压后，使用以下命令执行：\n\t\n\tjava -jar -Dfile.encoding='utf-8' webmagic-console.jar -f 脚本文件名 [-l 语言，默认是javascript] [-t 线程数] [-s 抓取间隔，毫秒] url1 url2 …\n\n例如，对于github这个脚本，我可以这样执行：\n\n\tjava -jar -Dfile.encoding='utf-8' webmagic-console.jar -f github.js -t 2 -s 0 https://github.com/code4craft\n\n目前这部分使用Java的ScriptEngine机制完成。\n\n## 语言:\n\n选用javascript是因为用户面比较广。目前还支持ruby语言，选用ruby是因为ruby的语法编写DSL更简洁：\n\n```ruby\nname= xpath \"//h1[@class='entry-title public']/strong/a/text()\"\nreadme = xpath \"//div[@id='readme']/tidyText()\"\nstar = xpath \"//ul[@class='pagehead-actions']/li[1]//a[@class='social-count js-social-count']/text()\"\nfork = xpath \"//ul[@class='pagehead-actions']/li[2]//a[@class='social-count']/text()\"\nurl=$page.getUrl().toString()\n\nputs name,readme,star,fork,url unless name==nil\n\nurls \"(https://github\\\\.com/\\\\w+/\\\\w+)\"\nurls \"(https://github\\\\.com/\\\\w+)\"\n```\n\n多语言通过参数-l区分，例如执行这个ruby脚本需要：\n\n\tjava -jar -Dfile.encoding='utf-8' webmagic-console.jar -f github.rb -t2 -s0 -l ruby https://github.com/code4craft\n\n这个功能目前仍在实验阶段。欢迎大家积极参与并提出意见。"
  },
  {
    "path": "webmagic-scripts/deploy.sh",
    "content": "#!/bin/sh\nVERSION=\"0.4.1-SNAPSHOT\"\nmvn clean package\ncp target/webmagic-scripts-${VERSION}.jar /usr/local/webmagic/webmagic-console.jar\nrsync -avz --delete target/lib/ /usr/local/webmagic/lib/\n"
  },
  {
    "path": "webmagic-scripts/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n    xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://maven.apache.org/POM/4.0.0\n        http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>us.codecraft</groupId>\n        <artifactId>webmagic</artifactId>\n        <version>1.0.4-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>webmagic-scripts</artifactId>\n    <properties>\n        <kotlin.version>2.1.0</kotlin.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-slf4j2-impl</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.jruby</groupId>\n            <artifactId>jruby</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.jetbrains.kotlin</groupId>\n            <artifactId>kotlin-stdlib</artifactId>\n            <version>${kotlin.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.python</groupId>\n            <artifactId>jython</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>commons-cli</groupId>\n            <artifactId>commons-cli</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-core</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-extension</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <version>1.18.32</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addClasspath>true</addClasspath>\n                            <classpathPrefix>./lib/</classpathPrefix>\n                            <mainClass>us.codecraft.webmagic.scripts.ScriptConsole</mainClass>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>build-helper-maven-plugin</artifactId>\n                <version>3.0.0</version>\n                <executions>\n                    <execution>\n                        <id>add-source</id>\n                        <phase>generate-sources</phase>\n                        <goals>\n                            <goal>add-source</goal>\n                        </goals>\n                        <configuration>\n                            <sources>\n                                <source>${project.basedir}/src/main/kotlin</source>\n                            </sources>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n\n</project>\n"
  },
  {
    "path": "webmagic-scripts/src/main/groovy/Github.groovy",
    "content": "Github {\n    Site {\n        sleepTime 0\n        timeOut 100\n        retryTimes 3\n        userAgent ['a','b','c'].random\n    }\n    match \"https://github.com/\\\\w+/\\\\w+\" {\n        addUrl(url.regex(\"https://github.com/\\\\w+/\\\\w+\"))\n        return  {\n            name: html.xpath(\"//h1[@class='entry-title public']/strong/a/text()\")\n            author: html.xpath \"https://github\\\\.com/(\\\\w+)/.*\"\n            readme: html.xpath \"//div[@id='readme']/tidyText()\"\n            star : toInt(html.xpath(\"//div[@id='readme']/tidyText()\"))\n        }\n    }\n\n}\n"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/Params.java",
    "content": "package us.codecraft.webmagic.scripts;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport us.codecraft.webmagic.scripts.languages.JRuby;\nimport us.codecraft.webmagic.scripts.languages.Javascript;\nimport us.codecraft.webmagic.scripts.languages.Language;\nimport us.codecraft.webmagic.utils.WMCollections;\n\npublic class Params {\n   @Getter\n   Language language = new Javascript();\n\n   @Getter @Setter\n   String scriptFileName;\n\n   @Getter @Setter\n   List<String> urls;\n\n   @Getter @Setter\n   int thread = 1;\n\n   @Getter @Setter\n   int sleepTime = 1000;\n   \n   private static Map<Language, Set<String>> alias;\n\n   public Params() {\n      alias = new HashMap<Language, Set<String>>();\n      alias.put(new Javascript(), WMCollections.<String>newHashSet(\"js\", \"javascript\", \"JavaScript\", \"JS\"));\n      alias.put(new JRuby(), WMCollections.<String>newHashSet(\"ruby\", \"jruby\", \"Ruby\", \"JRuby\"));\n   }\n\n   public void setLanguagefromArg(String arg) {\n       for (Map.Entry<Language, Set<String>> languageSetEntry : alias.entrySet()) {\n           if (languageSetEntry.getValue().contains(arg)) {\n               this.language = languageSetEntry.getKey();\n               return;\n           }\n       }\n   }\n}"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/ScriptConsole.java",
    "content": "package us.codecraft.webmagic.scripts;\n\nimport org.apache.commons.cli.*;\n\nimport us.codecraft.webmagic.ResultItems;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.pipeline.Pipeline;\nimport us.codecraft.webmagic.scripts.config.CommandLineOption;\nimport us.codecraft.webmagic.utils.WMCollections;\n\nimport java.util.List;\n\n/**\n * @author code4crafter@gmail.com / FrancoisGib\n * @since 0.4.1\n */\npublic class ScriptConsole {\n    public static void main(String[] args) {\n        Params params = parseCommand(args);\n        startSpider(params);\n    }\n\n    private static void startSpider(Params params) {\n        ScriptProcessor pageProcessor = ScriptProcessorBuilder.custom()\n                .language(params.getLanguage()).scriptFromFile(params.getScriptFileName()).thread(params.getThread()).build();\n        pageProcessor.getSite().setSleepTime(params.getSleepTime());\n        pageProcessor.getSite().setRetryTimes(3);\n        pageProcessor.getSite().setAcceptStatCode(WMCollections.<Integer>newHashSet(200, 404,403, 500,502));\n        Spider spider = Spider.create(pageProcessor).thread(params.getThread());\n        spider.clearPipeline().addPipeline(new Pipeline() {\n            @Override\n            public void process(ResultItems resultItems, Task task) {\n\n            }\n        });\n        if (params.getUrls() == null || params.getUrls().size() == 0) {\n            System.err.println(\"Need at least one argument\");\n            System.out.println(\"Usage: java -jar webmagic.jar [-l language] -f script file [-t threadnum] [-s sleep time] url1 [url2 url3]\");\n            System.exit(-1);\n        }\n        for (String url : params.getUrls()) {\n            spider.addUrl(url);\n        }\n        spider.run();\n    }\n\n\n    private static Params parseCommand(String[] args) {\n        try {\n            Options options = new Options();\n            options.addOption(new Option(\"l\", \"language\", true, \"language\"));\n            options.addOption(new Option(\"t\", \"thread\", true, \"thread\"));\n            options.addOption(new Option(\"f\", \"file\", true, \"script file\"));\n            options.addOption(new Option(\"i\", \"input\", true, \"input file\"));\n            options.addOption(new Option(\"s\", \"sleep\", true, \"sleep time\"));\n            options.addOption(new Option(\"g\", \"logger\", true, \"sleep time\"));\n            CommandLineParser commandLineParser = new PosixParser();\n            CommandLine commandLine = commandLineParser.parse(options, args);\n            return readOptions(commandLine);\n        } catch (Exception e) {\n            e.printStackTrace();\n            exit();\n            return null;\n        }\n    }\n\n    private static void exit() {\n        System.err.println(\"Format error\");\n        System.out.println(\"Usage: java -jar webmagic.jar [-l language] -f script file [-t threadnum] [-s sleep time] url1 [url2 url3]\");\n        System.exit(-1);\n    }\n\n    private static Params readOptions(CommandLine commandLine) {\n        Params params = new Params();\n        List<CommandLineOption> options = CommandLineOption.getAllOptions();\n        for (CommandLineOption option : options)\n            option.addParamOptionIfInCommandLine(params, commandLine);\n        return params;\n    }\n}"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/ScriptEnginePool.java",
    "content": "package us.codecraft.webmagic.scripts;\n\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptEngineManager;\n\nimport us.codecraft.webmagic.scripts.languages.Language;\n\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.4.1\n */\npublic class ScriptEnginePool {\n\n    private final AtomicInteger availableCount;\n\n    private final LinkedBlockingQueue<ScriptEngine> scriptEngines = new LinkedBlockingQueue<ScriptEngine>();\n\n    public ScriptEnginePool(Language language,int size) {\n        this.availableCount = new AtomicInteger(size);\n        for (int i=0;i<size;i++){\n            ScriptEngineManager manager = new ScriptEngineManager();\n            ScriptEngine engine = manager.getEngineByName(language.getEngineName());\n            scriptEngines.add(engine);\n        }\n    }\n\n    public ScriptEngine getEngine() {\n        availableCount.decrementAndGet();\n        return scriptEngines.poll();\n    }\n\n    public void release(ScriptEngine scriptEngine){\n        scriptEngines.add(scriptEngine);\n        availableCount.incrementAndGet();\n    }\n\n}\n"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/ScriptProcessor.java",
    "content": "package us.codecraft.webmagic.scripts;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport javax.script.ScriptContext;\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptException;\nimport org.apache.commons.io.IOUtils;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.processor.PageProcessor;\nimport us.codecraft.webmagic.scripts.languages.Language;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.4.1\n */\npublic class ScriptProcessor implements PageProcessor {\n\n    private ScriptEnginePool enginePool;\n\n    private String defines;\n\n    private String script;\n\n    private final Language language;\n\n    private Site site = Site.me();\n\n    public ScriptProcessor(Language language, String script, int threadNum) {\n        if (language == null || script == null) {\n            throw new IllegalArgumentException(\"language and script must not be null!\");\n        }\n        this.language = language;\n        enginePool = new ScriptEnginePool(language, threadNum);\n        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(language.getDefineFile());\n        try {\n            defines = IOUtils.toString(resourceAsStream, Charset.defaultCharset());\n        } catch (IOException e) {\n            throw new IllegalArgumentException(e);\n        }\n        this.script = script;\n    }\n\n    @Override\n    public void process(Page page) {\n        ScriptEngine engine = enginePool.getEngine();\n        try {\n            ScriptContext context = engine.getContext();\n            context.setAttribute(\"page\", page, ScriptContext.ENGINE_SCOPE);\n            context.setAttribute(\"config\", site, ScriptContext.ENGINE_SCOPE);\n            try {\n                this.language.process(engine, defines, script, page);\n            } catch (ScriptException e) {\n                e.printStackTrace();\n            }\n        } finally {\n            enginePool.release(engine);\n        }\n    }\n\n\n    @Override\n    public Site getSite() {\n        return site;\n    }\n\n}\n"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/ScriptProcessorBuilder.java",
    "content": "package us.codecraft.webmagic.scripts;\n\n\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport org.apache.commons.io.IOUtils;\n\nimport us.codecraft.webmagic.scripts.languages.Javascript;\nimport us.codecraft.webmagic.scripts.languages.Language;\n\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.4.1\n */\npublic class ScriptProcessorBuilder {\n\n    private static final Language DefaultLanguage = new Javascript();\n\n    private Language language = DefaultLanguage;\n\n    private String script;\n\n    private int threadNum = 1;\n\n    private ScriptProcessorBuilder() {\n    }\n\n    public static ScriptProcessorBuilder custom() {\n        return new ScriptProcessorBuilder();\n    }\n\n    public ScriptProcessorBuilder language(Language language) {\n        this.language = language;\n        return this;\n    }\n\n    public ScriptProcessorBuilder scriptFromFile(String fileName) {\n        try {\n            InputStream resourceAsStream = new FileInputStream(fileName);\n            this.script = IOUtils.toString(resourceAsStream, Charset.defaultCharset());\n        } catch (IOException e) {\n            throw new IllegalArgumentException(e);\n        }\n        return this;\n    }\n\n    public ScriptProcessorBuilder scriptFromClassPathFile(String fileName) {\n        try {\n            InputStream resourceAsStream = ScriptProcessor.class.getClassLoader().getResourceAsStream(fileName);\n            this.script = IOUtils.toString(resourceAsStream, Charset.defaultCharset());\n        } catch (IOException e) {\n            throw new IllegalArgumentException(e);\n        }\n        return this;\n    }\n\n    public ScriptProcessorBuilder script(String script) {\n        this.script = script;\n        return this;\n    }\n\n    public ScriptProcessorBuilder thread(int threadNum) {\n        this.threadNum = threadNum;\n        return this;\n    }\n\n    public ScriptProcessor build(){\n        return new ScriptProcessor(language,script,threadNum);\n    }\n\n}\n"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/config/CommandLineOption.java",
    "content": "package us.codecraft.webmagic.scripts.config;\n\nimport java.util.List;\n\nimport org.apache.commons.cli.CommandLine;\n\nimport lombok.Getter;\nimport us.codecraft.webmagic.scripts.Params;\n\npublic abstract class CommandLineOption {\n    @Getter\n    char option;\n\n    public CommandLineOption(char option) {\n        this.option = option;\n    }\n\n    protected abstract void addParamOption(Params params, CommandLine commandLine);\n\n    public void addParamOptionIfInCommandLine(Params params, CommandLine commandLine) {\n        if (commandLine.hasOption(this.option))\n            this.addParamOption(params, commandLine);\n    }\n\n    public static List<CommandLineOption> getAllOptions() {\n        return List.of(new OptionL(), new OptionF(), new OptionS(), new OptionT(), new OptionG());\n    }\n}\n\nclass OptionL extends CommandLineOption {\n    public OptionL() {\n        super('l');\n    }\n\n    protected void addParamOption(Params params, CommandLine commandLine) {\n        String language = commandLine.getOptionValue(\"l\");\n        params.setLanguagefromArg(language);\n    }\n}\n\nclass OptionF extends CommandLineOption {\n    public OptionF() {\n        super('f');\n    }\n\n    protected void addParamOption(Params params, CommandLine commandLine) {\n        String scriptFilename = commandLine.getOptionValue(\"f\");\n        params.setScriptFileName(scriptFilename);\n    }\n}\n\nclass OptionS extends CommandLineOption {\n    public OptionS() {\n        super('s');\n    }\n\n    protected void addParamOption(Params params, CommandLine commandLine) {\n        Integer sleepTime = Integer.parseInt(commandLine.getOptionValue(\"s\"));\n        params.setSleepTime(sleepTime);\n    }\n}\n\nclass OptionT extends CommandLineOption {\n    public OptionT() {\n        super('t');\n    }\n\n    protected void addParamOption(Params params, CommandLine commandLine) {\n        Integer thread = Integer.parseInt(commandLine.getOptionValue(\"t\"));\n        params.setThread(thread);\n    }\n}\n\nclass OptionG extends CommandLineOption {\n    public OptionG() {\n        super('g');\n    }\n\n    protected void addParamOption(Params params, CommandLine commandLine) {\n        ConfigLogger.configLogger(commandLine.getOptionValue(\"g\"));\n    }\n}"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/config/ConfigLogger.java",
    "content": "package us.codecraft.webmagic.scripts.config;\n\nimport java.util.List;\n\nimport org.apache.commons.lang3.tuple.Pair;\nimport org.apache.logging.log4j.Level;\nimport org.apache.logging.log4j.core.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class ConfigLogger {\n    /**\n     * Log the config parameter. If the counter is less than the number of available\n     * options then it means that the user entered an option\n     * \n     * @param value The config string\n     */\n    public static void configLogger(String value) {\n        List<Pair<String, Level>> options = List.of(\n            Pair.of(\"debug\", Level.DEBUG),\n            Pair.of(\"info\", Level.INFO),\n            Pair.of(\"warn\", Level.WARN),\n            Pair.of(\"trace\", Level.TRACE),\n            Pair.of(\"off\", Level.OFF),\n            Pair.of(\"error\", Level.ERROR));\n        Pair<String, Level> option = options.get(0);\n        int i = 1;\n        while (i < options.size() && !option.getLeft().equalsIgnoreCase(value))\n            option = options.get(i++);\n        if (i < options.size()) {\n            Logger rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);\n            rootLogger.setLevel(option.getRight());\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/languages/JRuby.java",
    "content": "package us.codecraft.webmagic.scripts.languages;\n\nimport java.util.Iterator;\nimport java.util.Map;\n\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptException;\n\nimport org.jruby.RubyHash;\n\nimport us.codecraft.webmagic.Page;\n\npublic class JRuby extends Language {\n    public JRuby() {\n        super(\"jruby\",\"ruby/defines.rb\",\"\");\n    }\n\n    public void process(ScriptEngine engine, String defines, String script, Page page) throws ScriptException {\n        RubyHash oRuby = (RubyHash) engine.eval(defines + \"\\n\" + script, engine.getContext());\n        Iterator itruby = oRuby.entrySet().iterator();\n        while (itruby.hasNext()) {\n            Map.Entry pairs = (Map.Entry) itruby.next();\n            page.getResultItems().put(pairs.getKey().toString(), pairs.getValue());\n        }\n    }\n} "
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/languages/Javascript.java",
    "content": "package us.codecraft.webmagic.scripts.languages;\n\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptException;\n\nimport us.codecraft.webmagic.Page;\n\npublic class Javascript extends Language {\n    public Javascript() {\n        super(\"javascript\",\"js/defines.js\",\"\");\n    }\n\n    public void process(ScriptEngine engine, String defines, String script, Page page) throws ScriptException {\n        engine.eval(defines + \"\\n\" + script, engine.getContext());\n    }\n}"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/languages/Jython.java",
    "content": "package us.codecraft.webmagic.scripts.languages;\n\nimport java.util.Iterator;\nimport java.util.Map;\n\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptException;\n\nimport org.python.core.PyDictionary;\n\nimport us.codecraft.webmagic.Page;\n\npublic class Jython extends Language {\n    public Jython() {\n        super(\"jython\",\"python/defines.py\",\"\");\n    }\n\n    public void process(ScriptEngine engine, String defines, String script, Page page) throws ScriptException {\n        engine.eval(defines + \"\\n\" + script, engine.getContext());\n        PyDictionary oJython = (PyDictionary) engine.get(\"result\");\n        Iterator it = oJython.entrySet().iterator();\n        while (it.hasNext()) {\n            Map.Entry pairs = (Map.Entry) it.next();\n            page.getResultItems().put(pairs.getKey().toString(), pairs.getValue());\n        }\n    }\n}"
  },
  {
    "path": "webmagic-scripts/src/main/java/us/codecraft/webmagic/scripts/languages/Language.java",
    "content": "package us.codecraft.webmagic.scripts.languages;\n\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptException;\nimport us.codecraft.webmagic.Page;\n\n/**\n * @author FrancoisGib\n */\npublic abstract class Language {\n    public Language(String engineName, String defineFile, String gatherFile) {\n        this.engineName = engineName;\n        this.defineFile = defineFile;\n        this.gatherFile = gatherFile;\n    }\n\n    private String engineName;\n\n    private String defineFile;\n\n    private String gatherFile;\n\n    public String getEngineName() {\n        return engineName;\n    }\n\n    public String getDefineFile() {\n        return defineFile;\n    }\n\n    public String getGatherFile() {\n        return gatherFile;\n    }\n\n    public abstract void process(ScriptEngine engine, String defines, String script, Page page) throws ScriptException;\n}\n"
  },
  {
    "path": "webmagic-scripts/src/main/kotlin/Github.kt",
    "content": "\nimport us.codecraft.webmagic.Page\nimport us.codecraft.webmagic.Site\nimport us.codecraft.webmagic.Spider\nimport us.codecraft.webmagic.processor.PageProcessor\nimport us.codecraft.webmagic.processor.example.GithubRepoPageProcessor\n\n/**\n *\n * @author code4crafter@gmail.com\n * Date: 2017/5/31\n * Time: 下午11:33\n *\n */\nclass GithubRepoPageProcessor : PageProcessor {\n\n    private val site = Site.me().setRetryTimes(3).setSleepTime(1000).setTimeOut(10000)\n\n    override fun process(page: Page) {\n        page.addTargetRequests(page.html.links().regex(\"(https://github\\\\.com/[\\\\w\\\\-]+/[\\\\w\\\\-]+)\").all())\n        page.addTargetRequests(page.html.links().regex(\"(https://github\\\\.com/[\\\\w\\\\-])\").all())\n        page.putField(\"author\", page.url.regex(\"https://github\\\\.com/(\\\\w+)/.*\").toString())\n        page.putField(\"name\", page.html.xpath(\"//h1[@class='public']/strong/a/text()\").toString())\n        if (page.resultItems.get<Any>(\"name\") == null) {\n            //skip this page\n            page.setSkip(true)\n        }\n        page.putField(\"readme\", page.html.xpath(\"//div[@id='readme']/tidyText()\"))\n    }\n\n    override fun getSite(): Site {\n        return site\n    }\n\n    companion object {\n        @JvmStatic fun main(args: Array<String>) {\n            Spider.create(GithubRepoPageProcessor()).addUrl(\"https://github.com/code4craft\").thread(5).run()\n        }\n    }\n}\n"
  },
  {
    "path": "webmagic-scripts/src/main/resources/js/defines.js",
    "content": "function $(str){\n    return page.getHtml().$(str).toString();\n}\nfunction xpath(str){\n    return page.getHtml().xpath(str).toString();\n}\nfunction urls(str){\n    links = page.getHtml().links().regex(str).all();\n    page.addTargetRequests(links);\n}\n"
  },
  {
    "path": "webmagic-scripts/src/main/resources/js/github.js",
    "content": "var name=xpath(\"//h1[@class='entry-title public']/strong/a/text()\")\nvar readme=xpath(\"//div[@id='readme']/tidyText()\")\nvar star=xpath(\"//ul[@class='pagehead-actions']/li[1]//a[@class='social-count js-social-count']/text()\")\nvar fork=xpath(\"//ul[@class='pagehead-actions']/li[2]//a[@class='social-count']/text()\")\nvar url=page.getUrl().toString()\nif (name!=null){\n    println(name)\n    println(readme)\n    println(star)\n    println(url)\n}\n\nurls(\"(https://github\\\\.com/\\\\w+/\\\\w+)\")\nurls(\"(https://github\\\\.com/\\\\w+)\")"
  },
  {
    "path": "webmagic-scripts/src/main/resources/js/oschina.js",
    "content": "var result = {\n    title: $(\"div.BlogTitle h1\"),\n    content: $(\"div.BlogContent\")\n}\nvar config = {\n    ua: '',\n    sleepTime : 20\n}\ntitle = $(\"div.BlogTitle h1\"),\ncontent = $(\"div.BlogContent\")\nurls(\"http://my\\\\.oschina\\\\.net/flashsword/blog/\\\\d+\")\nconfig;\n"
  },
  {
    "path": "webmagic-scripts/src/main/resources/python/defines.py",
    "content": "def xpath(str):\n   return page.getHtml().xpath(str).toString()\n\ndef css(str):\n  return page.getHtml().css(str).toString()\n\ndef urls(str):\n  links=page.getHtml().links().regex(str).all()\n  page.addTargetRequests(links);\n\ndef tomap(key,value):\n  return \"hello world\"\n\n"
  },
  {
    "path": "webmagic-scripts/src/main/resources/python/oschina.py",
    "content": "title=xpath(\"div[@class=BlogTitle]\")\nurls=\"http://my\\\\.oschina\\\\.net/flashsword/blog/\\\\d+\"\n\nresult={\"title\":title,\"urls\":urls}\n"
  },
  {
    "path": "webmagic-scripts/src/main/resources/ruby/defines.rb",
    "content": "def xpath str\n  $page.getHtml().xpath(str).toString()\nend\ndef css str\n  $page.getHtml().css(str).toString()\nend\ndef urls str\n  links = $page.getHtml().links().regex(str).all();\n  $page.addTargetRequests(links);\nend\n\n"
  },
  {
    "path": "webmagic-scripts/src/main/resources/ruby/github.rb",
    "content": "name= xpath \"//h1[@class='entry-title public']/strong/a/text()\"\nreadme = xpath \"//div[@id='readme']/tidyText()\"\nstar = xpath \"//ul[@class='pagehead-actions']/li[1]//a[@class='social-count js-social-count']/text()\"\nfork = xpath \"//ul[@class='pagehead-actions']/li[2]//a[@class='social-count']/text()\"\nurl=$page.getUrl().toString()\n\nputs name,readme,star,fork,url unless name==nil\n\nurls \"(https://github\\\\.com/\\\\w+/\\\\w+)\"\nurls \"(https://github\\\\.com/\\\\w+)\""
  },
  {
    "path": "webmagic-scripts/src/main/resources/ruby/oschina.rb",
    "content": "urls \"http://my\\\\.oschina\\\\.net/flashsword/blog/\\\\d+\"\ntitle = css \"div.BlogTitle h1\"\ncontent = css \"div.BlogContent\"\n\nreturn {\"title\"=>title,\"content\"=>content}\n\n"
  },
  {
    "path": "webmagic-scripts/src/test/java/us/codecraft/webmagic/scripts/ScriptProcessorTest.java",
    "content": "package us.codecraft.webmagic.scripts;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\n\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.scripts.languages.JRuby;\nimport us.codecraft.webmagic.scripts.languages.Javascript;\nimport us.codecraft.webmagic.scripts.languages.Jython;\n\n/**\n * @author code4crafter@gmail.com\n * @since 0.4.1\n */\n@Ignore\npublic class ScriptProcessorTest {\n\n    @Test\n    public void testJavaScriptProcessor() {\n        ScriptProcessor pageProcessor = ScriptProcessorBuilder.custom().language(new Javascript()).scriptFromClassPathFile(\"js/oschina.js\").build();\n        pageProcessor.getSite().setSleepTime(0);\n        Spider.create(pageProcessor).addUrl(\"http://my.oschina.net/flashsword/blog\").setSpawnUrl(false).run();\n    }\n\n    @Test\n    public void testRubyProcessor() {\n        ScriptProcessor pageProcessor = ScriptProcessorBuilder.custom().language(new JRuby()).scriptFromClassPathFile(\"ruby/oschina.rb\").build();\n        pageProcessor.getSite().setSleepTime(0);\n        Spider.create(pageProcessor).addUrl(\"http://my.oschina.net/flashsword/blog\").setSpawnUrl(false).run();\n    }\n\n\n    @Test\n    public void testPythonProcessor() {\n        ScriptProcessor pageProcessor = ScriptProcessorBuilder.custom().language(new Jython()).scriptFromClassPathFile(\"python/oschina.py\").build();\n        pageProcessor.getSite().setSleepTime(0);\n        Spider.create(pageProcessor).addUrl(\"http://my.oschina.net/flashsword/blog\").setSpawnUrl(false).run();\n    }\n}\n"
  },
  {
    "path": "webmagic-scripts/src/test/resources/log4j2-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration>\n    <Appenders>\n        <Console name=\"stdout\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"%d{yy-MM-dd HH:mm:ss,SSS} %-5p %c(%F:%L) ## %m%n\" />\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Logger name=\"org.apache\" level=\"warn\" additivity=\"false\">\n            <AppenderRef ref=\"stdout\" />\n        </Logger>\n        <Root level=\"debug\">\n            <AppenderRef ref=\"stdout\" />\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "webmagic-selenium/README.md",
    "content": "webmagic-extension\n-------\nwebmagic与selenium的集成，用于爬取ajax页面。selenium太重，所以单独抽出成一个包了。"
  },
  {
    "path": "webmagic-selenium/config.ini",
    "content": "# What WebDriver to use for the tests\ndriver=phantomjs\n#driver=firefox\n#driver=chrome\n#driver=http://localhost:8910\n#driver=http://localhost:4444/wd/hub\n\n# PhantomJS specific config (change according to your installation)\n#phantomjs_exec_path=/Users/Bingo/bin/phantomjs-qt5\nphantomjs_exec_path=/Users/Bingo/Downloads/phantomjs-1.9.8-macosx/bin/phantomjs\n#phantomjs_driver_path=/Users/Bingo/Documents/workspace/webmagic/webmagic-selenium/src/main.js\nphantomjs_driver_loglevel=DEBUG"
  },
  {
    "path": "webmagic-selenium/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n    xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"\n        http://maven.apache.org/POM/4.0.0\n        http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>us.codecraft</groupId>\n        <artifactId>webmagic</artifactId>\n        <version>1.0.4-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>webmagic-selenium</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.seleniumhq.selenium</groupId>\n            <artifactId>selenium-java</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>webmagic-core</artifactId>\n            <version>${project.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.github.detro</groupId>\n            <artifactId>phantomjsdriver</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-deploy-plugin</artifactId>\n                <version>3.0.0-M1</version>\n                <configuration>\n                    <skip>true</skip>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "webmagic-selenium/src/main/java/us/codecraft/webmagic/downloader/selenium/SeleniumDownloader.java",
    "content": "package us.codecraft.webmagic.downloader.selenium;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.Cookie;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\nimport us.codecraft.webmagic.downloader.AbstractDownloader;\nimport us.codecraft.webmagic.selector.Html;\nimport us.codecraft.webmagic.selector.PlainText;\nimport us.codecraft.webmagic.utils.HttpConstant;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.net.http.HttpRequest;\nimport java.util.Map;\n\n/**\n * 使用Selenium调用浏览器进行渲染。目前仅支持chrome。<br>\n * 需要下载Selenium driver支持。<br>\n *\n * @author code4crafter@gmail.com <br>\n * Date: 13-7-26 <br>\n * Time: 下午1:37 <br>\n */\npublic class SeleniumDownloader extends AbstractDownloader implements Closeable {\n\n    private volatile WebDriverPool webDriverPool;\n\n    private Logger logger = LoggerFactory.getLogger(getClass());\n\n    private int sleepTime = 0;\n\n    private int poolSize = 1;\n\n    private static final String DRIVER_PHANTOMJS = \"phantomjs\";\n\n    /**\n     * 新建\n     *\n     * @param chromeDriverPath chromeDriverPath\n     */\n    public SeleniumDownloader(String chromeDriverPath) {\n        System.getProperties().setProperty(\"webdriver.chrome.driver\",\n                chromeDriverPath);\n    }\n\n    /**\n     * Constructor without any filed. Construct PhantomJS browser\n     *\n     * @author bob.li.0718@gmail.com\n     */\n    public SeleniumDownloader() {\n        // System.setProperty(\"phantomjs.binary.path\",\n        // \"/Users/Bingo/Downloads/phantomjs-1.9.7-macosx/bin/phantomjs\");\n    }\n\n    /**\n     * set sleep time to wait until load success\n     *\n     * @param sleepTime sleepTime\n     * @return this\n     */\n    public SeleniumDownloader setSleepTime(int sleepTime) {\n        this.sleepTime = sleepTime;\n        return this;\n    }\n\n    @Override\n    public Page download(Request request, Task task) {\n        checkInit();\n        WebDriver webDriver = null;\n        Page page = Page.fail(request);\n        try {\n            webDriver = webDriverPool.get();\n\n            logger.info(\"downloading page \" + request.getUrl());\n            webDriver.get(request.getUrl());\n            try {\n                if (sleepTime > 0) {\n                    Thread.sleep(sleepTime);\n                }\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            WebDriver.Options manage = webDriver.manage();\n            Site site = task.getSite();\n            if (site.getCookies() != null) {\n                for (Map.Entry<String, String> cookieEntry : site.getCookies()\n                        .entrySet()) {\n                    Cookie cookie = new Cookie(cookieEntry.getKey(),\n                            cookieEntry.getValue());\n                    manage.addCookie(cookie);\n                }\n            }\n\n            /*\n             * TODO You can add mouse event or other processes\n             *\n             * @author: bob.li.0718@gmail.com\n             */\n\n            WebElement webElement = webDriver.findElement(By.xpath(\"/html\"));\n            String content = webElement.getAttribute(\"outerHTML\");\n            page.setDownloadSuccess(true);\n            page.setRawText(content);\n            page.setHtml(new Html(content, request.getUrl()));\n            page.setUrl(new PlainText(request.getUrl()));\n            page.setRequest(request);\n            page.setStatusCode(HttpConstant.StatusCode.CODE_200);\n            onSuccess(page, task);\n        } catch (Exception e) {\n            logger.warn(\"download page {} error\", request.getUrl(), e);\n            onError(page, task, e);\n        } finally {\n            if (webDriver != null) {\n                webDriverPool.returnToPool(webDriver);\n            }\n        }\n        return page;\n    }\n\n    private void checkInit() {\n        if (webDriverPool == null) {\n            synchronized (this) {\n                webDriverPool = new WebDriverPool(poolSize);\n            }\n        }\n    }\n\n    @Override\n    public void setThread(int thread) {\n        this.poolSize = thread;\n    }\n\n    @Override\n    public void close() throws IOException {\n        webDriverPool.closeAll();\n    }\n}\n"
  },
  {
    "path": "webmagic-selenium/src/main/java/us/codecraft/webmagic/downloader/selenium/WebDriverPool.java",
    "content": "package us.codecraft.webmagic.downloader.selenium;\n\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.concurrent.BlockingDeque;\nimport java.util.concurrent.LinkedBlockingDeque;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.chrome.ChromeDriver;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.firefox.FirefoxDriver;\nimport org.openqa.selenium.firefox.FirefoxOptions;\nimport org.openqa.selenium.phantomjs.PhantomJSDriver;\nimport org.openqa.selenium.phantomjs.PhantomJSDriverService;\nimport org.openqa.selenium.remote.DesiredCapabilities;\nimport org.openqa.selenium.remote.RemoteWebDriver;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * @author code4crafter@gmail.com <br>\n *         Date: 13-7-26 <br>\n *         Time: 下午1:41 <br>\n */\nclass WebDriverPool {\n\tprivate Logger logger = LoggerFactory.getLogger(getClass());\n\n\tprivate final static int DEFAULT_CAPACITY = 5;\n\n\tprivate final int capacity;\n\n\tprivate final static int STAT_RUNNING = 1;\n\n\tprivate final static int STAT_CLODED = 2;\n\n\tprivate AtomicInteger stat = new AtomicInteger(STAT_RUNNING);\n\n\t/*\n\t * new fields for configuring phantomJS\n\t */\n\tprivate WebDriver mDriver = null;\n\tprivate boolean mAutoQuitDriver = true;\n\n\tprivate static final String DEFAULT_CONFIG_FILE = \"/data/webmagic/webmagic-selenium/config.ini\";\n\tprivate static final String DRIVER_FIREFOX = \"firefox\";\n\tprivate static final String DRIVER_CHROME = \"chrome\";\n\tprivate static final String DRIVER_PHANTOMJS = \"phantomjs\";\n\n\tprotected static Properties sConfig;\n\tprotected static DesiredCapabilities sCaps;\n\n\t/**\n\t * Configure the GhostDriver, and initialize a WebDriver instance. This part\n\t * of code comes from GhostDriver.\n\t * https://github.com/detro/ghostdriver/tree/master/test/java/src/test/java/ghostdriver\n\t *\n\t * @author bob.li.0718@gmail.com\n\t * @throws IOException\n\t */\n\tpublic void configure() throws IOException {\n\t\t// Read config file\n\t\tsConfig = new Properties();\n\t\tString configFile = DEFAULT_CONFIG_FILE;\n\t\tif (System.getProperty(\"selenuim_config\")!=null){\n\t\t\tconfigFile = System.getProperty(\"selenuim_config\");\n\t\t}\n\t\tsConfig.load(new FileReader(configFile));\n\n\t\t// Prepare capabilities\n\t\tsCaps = new DesiredCapabilities();\n\t\tsCaps.setCapability(\"takesScreenshot\", false);\n\n\t\tString driver = sConfig.getProperty(\"driver\", DRIVER_PHANTOMJS);\n\n\t\t// Fetch PhantomJS-specific configuration parameters\n\t\tif (driver.equals(DRIVER_PHANTOMJS)) {\n\t\t\t// \"phantomjs_exec_path\"\n\t\t\tif (sConfig.getProperty(\"phantomjs_exec_path\") != null) {\n\t\t\t\tsCaps.setCapability(\n\t\t\t\t\t\tPhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY,\n\t\t\t\t\t\tsConfig.getProperty(\"phantomjs_exec_path\"));\n\t\t\t} else {\n\t\t\t\tthrow new IOException(\n\t\t\t\t\t\tString.format(\n\t\t\t\t\t\t\t\t\"Property '%s' not set!\",\n\t\t\t\t\t\t\t\tPhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY));\n\t\t\t}\n\t\t\t// \"phantomjs_driver_path\"\n\t\t\tif (sConfig.getProperty(\"phantomjs_driver_path\") != null) {\n\t\t\t\tSystem.out.println(\"Test will use an external GhostDriver\");\n\t\t\t\tsCaps.setCapability(\n\t\t\t\t\t\tPhantomJSDriverService.PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY,\n\t\t\t\t\t\tsConfig.getProperty(\"phantomjs_driver_path\"));\n\t\t\t} else {\n\t\t\t\tSystem.out\n\t\t\t\t\t\t.println(\"Test will use PhantomJS internal GhostDriver\");\n\t\t\t}\n\t\t}\n\n\t\t// Disable \"web-security\", enable all possible \"ssl-protocols\" and\n\t\t// \"ignore-ssl-errors\" for PhantomJSDriver\n\t\t// sCaps.setCapability(PhantomJSDriverService.PHANTOMJS_CLI_ARGS, new\n\t\t// String[] {\n\t\t// \"--web-security=false\",\n\t\t// \"--ssl-protocol=any\",\n\t\t// \"--ignore-ssl-errors=true\"\n\t\t// });\n\n\t\tArrayList<String> cliArgsCap = new ArrayList<String>();\n\t\tcliArgsCap.add(\"--web-security=false\");\n\t\tcliArgsCap.add(\"--ssl-protocol=any\");\n\t\tcliArgsCap.add(\"--ignore-ssl-errors=true\");\n\t\tsCaps.setCapability(PhantomJSDriverService.PHANTOMJS_CLI_ARGS,\n\t\t\t\tcliArgsCap);\n\n\t\t// Control LogLevel for GhostDriver, via CLI arguments\n\t\tsCaps.setCapability(\n\t\t\t\tPhantomJSDriverService.PHANTOMJS_GHOSTDRIVER_CLI_ARGS,\n\t\t\t\tnew String[] { \"--logLevel=\"\n\t\t\t\t\t\t+ (sConfig.getProperty(\"phantomjs_driver_loglevel\") != null ? sConfig\n\t\t\t\t\t\t\t\t.getProperty(\"phantomjs_driver_loglevel\")\n\t\t\t\t\t\t\t\t: \"INFO\") });\n\n\t\t// String driver = sConfig.getProperty(\"driver\", DRIVER_PHANTOMJS);\n\n\t\t// Start appropriate Driver\n\t\tif (isUrl(driver)) {\n\t\t\tsCaps.setBrowserName(\"phantomjs\");\n\t\t\tmDriver = new RemoteWebDriver(new URL(driver), sCaps);\n\t\t} else if (driver.equals(DRIVER_FIREFOX)) {\n\t\t\tmDriver = new FirefoxDriver(new FirefoxOptions(sCaps));\n\t\t} else if (driver.equals(DRIVER_CHROME)) {\n\t\t\tmDriver = new ChromeDriver(new ChromeOptions().merge(sCaps));\n\t\t} else if (driver.equals(DRIVER_PHANTOMJS)) {\n\t\t\tmDriver = new PhantomJSDriver(sCaps);\n\t\t}\n\t}\n\n\t/**\n\t * check whether input is a valid URL\n\t *\n\t * @author bob.li.0718@gmail.com\n\t * @param urlString urlString\n\t * @return true means yes, otherwise no.\n\t */\n\tprivate boolean isUrl(String urlString) {\n\t\ttry {\n\t\t\tnew URL(urlString);\n\t\t\treturn true;\n\t\t} catch (MalformedURLException mue) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * store webDrivers created\n\t */\n\tprivate List<WebDriver> webDriverList = Collections\n\t\t\t.synchronizedList(new ArrayList<WebDriver>());\n\n\t/**\n\t * store webDrivers available\n\t */\n\tprivate BlockingDeque<WebDriver> innerQueue = new LinkedBlockingDeque<WebDriver>();\n\n\tpublic WebDriverPool(int capacity) {\n\t\tthis.capacity = capacity;\n\t}\n\n\tpublic WebDriverPool() {\n\t\tthis(DEFAULT_CAPACITY);\n\t}\n\n\t/**\n\t *\n\t * @return\n\t * @throws InterruptedException\n\t */\n\tpublic WebDriver get() throws InterruptedException {\n\t\tcheckRunning();\n\t\tWebDriver poll = innerQueue.poll();\n\t\tif (poll != null) {\n\t\t\treturn poll;\n\t\t}\n\t\tif (webDriverList.size() < capacity) {\n\t\t\tsynchronized (webDriverList) {\n\t\t\t\tif (webDriverList.size() < capacity) {\n\n\t\t\t\t\t// add new WebDriver instance into pool\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconfigure();\n\t\t\t\t\t\tinnerQueue.add(mDriver);\n\t\t\t\t\t\twebDriverList.add(mDriver);\n\t\t\t\t\t} catch (IOException e) {\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t}\n\n\t\t\t\t\t// ChromeDriver e = new ChromeDriver();\n\t\t\t\t\t// WebDriver e = getWebDriver();\n\t\t\t\t\t// innerQueue.add(e);\n\t\t\t\t\t// webDriverList.add(e);\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\treturn innerQueue.take();\n\t}\n\n\tpublic void returnToPool(WebDriver webDriver) {\n\t\tcheckRunning();\n\t\tinnerQueue.add(webDriver);\n\t}\n\n\tprotected void checkRunning() {\n\t\tif (!stat.compareAndSet(STAT_RUNNING, STAT_RUNNING)) {\n\t\t\tthrow new IllegalStateException(\"Already closed!\");\n\t\t}\n\t}\n\n\tpublic void closeAll() {\n\t\tboolean b = stat.compareAndSet(STAT_RUNNING, STAT_CLODED);\n\t\tif (!b) {\n\t\t\tthrow new IllegalStateException(\"Already closed!\");\n\t\t}\n\t\tfor (WebDriver webDriver : webDriverList) {\n\t\t\tlogger.info(\"Quit webDriver\" + webDriver);\n\t\t\twebDriver.quit();\n\t\t\twebDriver = null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "webmagic-selenium/src/test/java/us/codecraft/webmagic/downloader/SeleniumTest.java",
    "content": "package us.codecraft.webmagic.downloader;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.chrome.ChromeDriver;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.remote.DesiredCapabilities;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-7-26 <br>\n * Time: 下午12:27 <br>\n */\npublic class SeleniumTest {\n\n    @Ignore(\"need chrome driver\")\n    @Test\n    public void testSelenium() {\n        System.getProperties().setProperty(\"webdriver.chrome.driver\", \"/Users/yihua/Downloads/chromedriver\");\n        Map<String, Object> contentSettings = new HashMap<String, Object>();\n        contentSettings.put(\"images\", 2);\n\n        Map<String, Object> preferences = new HashMap<String, Object>();\n        preferences.put(\"profile.default_content_settings\", contentSettings);\n\n        DesiredCapabilities caps = new DesiredCapabilities();\n        caps.setCapability(\"chrome.prefs\", preferences);\n        caps.setCapability(\"chrome.switches\", Arrays.asList(\"--user-data-dir=/Users/yihua/temp/chrome\"));\n        WebDriver webDriver = new ChromeDriver(new ChromeOptions().merge(caps));\n        webDriver.get(\"http://huaban.com/\");\n        WebElement webElement = webDriver.findElement(By.xpath(\"/html\"));\n        System.out.println(webElement.getAttribute(\"outerHTML\"));\n        webDriver.close();\n    }\n}\n"
  },
  {
    "path": "webmagic-selenium/src/test/java/us/codecraft/webmagic/downloader/selenium/SeleniumDownloaderTest.java",
    "content": "package us.codecraft.webmagic.downloader.selenium;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Request;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Task;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-7-26 <br>\n *        Time: 下午2:46 <br>\n */\npublic class SeleniumDownloaderTest {\n\n\tprivate String chromeDriverPath = \"/Users/yihua/Downloads/chromedriver\";\n\n\t@Ignore(\"need chrome driver\")\n\t@Test\n\tpublic void test() {\n\t\tSeleniumDownloader seleniumDownloader = new SeleniumDownloader(chromeDriverPath);\n\t\tlong time1 = System.currentTimeMillis();\n\t\tfor (int i = 0; i < 100; i++) {\n\t\t\tPage page = seleniumDownloader.download(new Request(\"http://huaban.com/\"), new Task() {\n\t\t\t\t@Override\n\t\t\t\tpublic String getUUID() {\n\t\t\t\t\treturn \"huaban.com\";\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic Site getSite() {\n\t\t\t\t\treturn Site.me();\n\t\t\t\t}\n\t\t\t});\n\t\t\tSystem.out.println(page.getHtml().$(\"#waterfall\").links().regex(\".*pins.*\").all());\n\t\t}\n\t\tSystem.out.println(System.currentTimeMillis() - time1);\n\t}\n\n    @Ignore\n\t@Test\n\tpublic void testBaiduWenku() {\n\t\tSeleniumDownloader seleniumDownloader = new SeleniumDownloader(chromeDriverPath);\n        seleniumDownloader.setSleepTime(10000);\n\t\tlong time1 = System.currentTimeMillis();\n\t\tPage page = seleniumDownloader.download(new Request(\"http://wenku.baidu.com/view/462933ff04a1b0717fd5ddc2.html\"), new Task() {\n\t\t\t@Override\n\t\t\tpublic String getUUID() {\n\t\t\t\treturn \"huaban.com\";\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic Site getSite() {\n\t\t\t\treturn Site.me();\n\t\t\t}\n\t\t});\n\t\tSystem.out.println(page.getHtml().$(\"div.inner\").replace(\"<[^<>]+>\",\"\").replace(\"&nsbp;\",\"\").all());\n\t}\n\n}\n"
  },
  {
    "path": "webmagic-selenium/src/test/java/us/codecraft/webmagic/downloader/selenium/WebDriverPoolTest.java",
    "content": "package us.codecraft.webmagic.downloader.selenium;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport org.openqa.selenium.WebDriver;\n\n/**\n * @author code4crafter@gmail.com <br>\n * Date: 13-7-26 <br>\n * Time: 下午2:12 <br>\n */\npublic class WebDriverPoolTest {\n\n    private String chromeDriverPath = \"/Users/yihua/Downloads/chromedriver\";\n\n    @Ignore(\"need chrome driver\")\n    @Test\n    public void test() {\n        System.getProperties().setProperty(\"webdriver.chrome.driver\", chromeDriverPath);\n        WebDriverPool webDriverPool = new WebDriverPool(5);\n        for (int i = 0; i < 5; i++) {\n            try {\n                WebDriver webDriver = webDriverPool.get();\n                System.out.println(i);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n        webDriverPool.closeAll();\n    }\n}\n"
  },
  {
    "path": "webmagic-selenium/src/test/java/us/codecraft/webmagic/samples/GooglePlayProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.downloader.selenium.SeleniumDownloader;\nimport us.codecraft.webmagic.pipeline.FilePipeline;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n *\n * Using Selenium with PhantomJS to fetch web-page with JS<br>\n * \n * @author bob.li.0718@gmail.com <br>\n *         Date: 15-7-11 <br>\n */\npublic class GooglePlayProcessor implements PageProcessor {\n\n\tprivate Site site;\n\n\t@Override\n\tpublic void process(Page page) {\n\n\t\tpage.putField(\"whole-html\", page.getHtml().toString());\n\n\t}\n\n\t@Override\n\tpublic Site getSite() {\n\t\tif (null == site) {\n\t\t\tsite = Site.me().setDomain(\"play.google.com\").setSleepTime(300);\n\t\t}\n\t\treturn site;\n\t}\n\n\tpublic static void main(String[] args) {\n\t\tSpider.create(new GooglePlayProcessor())\n\t\t\t\t.thread(5)\n\t\t\t\t.addPipeline(\n\t\t\t\t\t\tnew FilePipeline(\n\t\t\t\t\t\t\t\t\"/Users/Bingo/Documents/workspace/webmagic/webmagic-selenium/data/\"))\n\t\t\t\t.setDownloader(new SeleniumDownloader())\n\t\t\t\t.addUrl(\"https://play.google.com/store/apps/details?id=com.tencent.mm\")\n\t\t\t\t.runAsync();\n\t}\n}\n"
  },
  {
    "path": "webmagic-selenium/src/test/java/us/codecraft/webmagic/samples/HuabanProcessor.java",
    "content": "package us.codecraft.webmagic.samples;\n\nimport us.codecraft.webmagic.Page;\nimport us.codecraft.webmagic.Site;\nimport us.codecraft.webmagic.Spider;\nimport us.codecraft.webmagic.downloader.selenium.SeleniumDownloader;\nimport us.codecraft.webmagic.pipeline.FilePipeline;\nimport us.codecraft.webmagic.processor.PageProcessor;\n\n/**\n * 花瓣网抽取器。<br>\n * 使用Selenium做页面动态渲染。<br>\n * @author code4crafter@gmail.com <br>\n * Date: 13-7-26 <br>\n * Time: 下午4:08 <br>\n */\npublic class HuabanProcessor implements PageProcessor {\n\n    private Site site;\n\n    @Override\n    public void process(Page page) {\n        page.addTargetRequests(page.getHtml().links().regex(\"http://huaban\\\\.com/.*\").all());\n        if (page.getUrl().toString().contains(\"pins\")) {\n            page.putField(\"img\", page.getHtml().xpath(\"//div[@class='image-holder']/a/img/@src\").toString());\n        } else {\n            page.getResultItems().setSkip(true);\n        }\n    }\n\n    @Override\n    public Site getSite() {\n        if (null == site) {\n            site = Site.me().setDomain(\"huaban.com\").setSleepTime(0);\n        }\n        return site;\n    }\n\n    public static void main(String[] args) {\n        Spider.create(new HuabanProcessor()).thread(5)\n                .addPipeline(new FilePipeline(\"/data/webmagic/test/\"))\n                .setDownloader(new SeleniumDownloader(\"/Users/yihua/Downloads/chromedriver\"))\n                .addUrl(\"http://huaban.com/\")\n                .runAsync();\n    }\n}\n"
  },
  {
    "path": "webmagic-selenium/src/test/resources/config.ini",
    "content": "#driver=phantomjs\n#driver=firefox\ndriver=chrome\n#driver=http://localhost:8910\ndriver=http://localhost:4444/wd/hub\n\n# PhantomJS specific config (change according to your installation)\n#phantomjs_exec_path=/Users/detro/bin/phantomjs-qt5\nphantomjs_exec_path=/Users/detro/bin/phantomjs-upstream\nphantomjs_driver_path=../../src/main.js\nphantomjs_driver_loglevel=DEBUG"
  }
]