<< 十一月 2015 | 首页 | 一月 2016 >>

oxf1/myspider · GitHub

  • 用户友好,一个管控界面解决全部问题
  • 新站点抓取无需重新部署
  • 天生分布式。支持集群动态扩、缩容
  • 精准解析。解析脚本化,支持Groovy, Javascript, python
  • 支持的去重方式:redis, mongodb, ehcache, 内存
  • 支持的队列: redis, mongodb(可靠抓取), ehcache, 内存
  • 支持伪分布式:一台机器模拟集群。
  • 支持大集群虚拟分组功能:隔离故障,降低维护成本。
  • 模块化:方便地使用脚本在运行时控制proxy,http header, url
  • 直接对接maven库,让部署新任务自动化、规范化
  • 分布式自动调度:无论单机还是多机都提供可靠的调度。不重复,不遗漏。
  • 提供低资源占用的js动态渲染解决方案:抓取ajax内容从此简单。
  • 增量抓取功能:新闻,股票,竞品数据...
  • 多页面数据合并功能:一条完整数据横跨几个页面?没关系!
  • 分页抓取功能,毫无乱序。
  • 提供辅助功能,利用机器学习实现无解析化抓取(实验特性)
  • 提供多种数据持久化策略:无论您想直接保存在db,还是希望实时处理,想要的都可以满足。
  • 登录:你懂的。
  • 验证码破解服务:你懂的,不罗嗦。
  • 提供多种变幻莫测的代理策略:当然你只需要管控界面上动动鼠标。
  • 解析太头疼?看看解析工具里的武器。表格,列表只需一条语句。
  • OCR太深奥?我都给你包好了,绝不放过任何一条数据。

阅读全文……

标签 : ,

karussell/snacktory · GitHub

Java正文抽取工具:

https://code.google.com/p/cx-extractor/

 

Snacktory

This is a small helper utility for people who don't want to write yet another java clone of Readability. In most cases, this is applied to articles, although it should work for any website to find its major area, extract its text, keywords, its main picture and more.

The resulting quality is high, even paper.li uses the core of snacktory. Also have a look into this article, it describes a news aggregator service which uses snacktory. But jetslide is no longer online.

Snacktory borrows some ideas and a lot of test cases from goose and jreadability:

阅读全文……

标签 : ,

5分钟开启Esper之旅 - Binhua Liu - 博客园

在我之前发表的文章中,我提到我最近热衷于Complex Event Processing (CEP) (复杂事件处理)。简单来说,CEP把数据流作为输入,根据一系列预定义的规则,把数据(或部分数据)重定向给监听者们;又或者是当发现数据中的隐含的模式(Pattern)时,触发事件。在大量数据被产生出来并需要进行实时地分析的场景下,CEP特别有用。

 

有一个很不错的软件项目,可以让你做到这一点,叫做ESPER。你可以在这里找到该项目的网站。Esper向程序员提供一个称为EPL的语言,有些类似于SQL语言,它可以很好地用于对规则和模式的配置,这些规则和模式将被应用到数据流上。

 

Esper附带的文档是相当完整的,但缺乏实际的例子,这使得它看起来难以被使用。所以这里给出一个Esper的5分钟指导。虽然下面的例子是用Java编写,但是其实Esper是同时支持Java和C#的。我这里假设你已经下载了Esper,如果没有,可以通过点击这个链接来完成。解压刚才下载的文件后,你应该在你磁盘的某个地方可以找到一个叫esper-3.1.0的文件夹。(译者:这篇文章比较早了,现在esper最新版本是5.2.0)

 

在开始前,你需要添加几个库文件到你的工程中,当然,esper-3.1.0.jar是其中之一,你也需要另外其他4个第三方的库文件,他们可以在esper-3.1.0.jar/esper/lib文件夹中找到。

 

现在开始我们的5分钟吧。在你把需要分析的数据丢到CEP引擎的管道中之前,需要把这些数据结构化到对象当中去。让我们用一个简单的例子,写一个类(或者叫它bean)来描述给定时间的某个股票的价格:

 

复制代码
import java.util.Date;     public static class Tick {         String symbol;         Double price;         Date timeStamp;           public Tick(String s, double p, long t) {             symbol = s;             price = p;             timeStamp = new Date(t);         }         public double getPrice() {return price;}         public String getSymbol() {return symbol;}         public Date getTimeStamp() {return timeStamp;}           @Override         public String toString() {             return "Price: " + price.toString() + " time: " + timeStamp.toString();         }     }
复制代码

 

它有3个属性:股票代码,价格和时间戳。在我们开始生成数以亿计的数据前,我们需要通知引擎它需要处理哪些对象,这是通过在实例化CEP引擎时,使用一个Configuration对象来实现的:

 

复制代码
import com.espertech.esper.client.*;   public class main {      public static void main(String [] args){       //The Configuration is meant only as an initialization-time object.
    Configuration cepConfig = new Configuration();     // We register Ticks as objects the engine will have to handle
    cepConfig.addEventType("StockTick",Tick.class.getName());      // We setup the engine
    EPServiceProvider cep = EPServiceProviderManager.getProvider("myCEPEngine",cepConfig);    } }
复制代码

 

作为测试目的,我们现在创建一个函数来生成随机的数据,并且把它们丢到CEP引擎当中,我们把这个函数叫做“GenerateRandomTick”,它把EPRuntime对象作为参数,这个对象用于把事件传递给CEP引擎:

 

复制代码
import java.util.Random; import com.espertech.esper.client.*;   public class exampleMain {    private static Random generator=new Random();      public static void GenerateRandomTick(EPRuntime cepRT){     double price = (double) generator.nextInt(10);     long timeStamp = System.currentTimeMillis();     String symbol = "AAPL";     Tick tick= new Tick(symbol,price,timeStamp);     System.out.println("Sending tick:" + tick);     cepRT.sendEvent(tick); }       public static void main(String[] args) {      //The Configuration is meant only as an initialization-time object.
      Configuration cepConfig = new Configuration();      cepConfig.addEventType("StockTick",Tick.class.getName());         EPServiceProvider cep=EPServiceProviderManager.getProvider("myCEPEngine",cepConfig);         EPRuntime cepRT = cep.getEPRuntime();     } }
复制代码

 

现在,我们有了一个可以工作的CEP引擎,和不断输入的虚假的数据,是时候来创建我们的第一条规则了,用Esper的说法,我们的第一条EPL语句。要这么做,我们需要请引擎的管理员记录我们的语句。然后,CEP引擎会根据EPL语句的定义,过滤它收到的数据,当数据满足语句中的选择条件或者模式时,触发事件。

复制代码
public static void main(String[] args) {     //The Configuration is meant only as an initialization-time object.
    Configuration cepConfig = new Configuration();     cepConfig.addEventType("StockTick",Tick.class.getName());     EPServiceProvider cep = EPServiceProviderManager.getProvider("myCEPEngine",cepConfig);     EPRuntime cepRT = cep.getEPRuntime();       // We register an EPL statement
    EPAdministrator cepAdm = cep.getEPAdministrator();     EPStatement cepStatement = cepAdm.createEPL("select * from " +
                                "StockTick(symbol='AAPL').win:length(2) " +
                                "having avg(price) > 6.0");   }
复制代码

 

这里,我们的规则设置为:每当最近的2次数据的平均值大于6.0时,触发事件。

 

下一步,主要是创建一个监听者并把它和我们的选择规则产生的事件关联起来。可以这么做:

 

cepStatement.addListener(new CEPListener());

 

这里有不同的方式来实现监听者,下面的是其中最简单的一种。这里监听者只是简单地把它从引擎中收到的对象打印出来:

 

复制代码
public static class CEPListener implements UpdateListener {  public void update(EventBean[] newData, EventBean[] oldData) {          System.out.println("Event received: "
                            + newData[0].getUnderlying());     } }
复制代码

 

到目前为止,看上去还不错。现在是测试我们的代码的时候了。让我们生成一些数据,看一切能否正常工作。可以在main函数中添加一下行来做到:

 

for(int i = 0; i< 5; i++)     GenerateRandomTick(cepRT);

 

现在所有的代码看上去如下所示(我把Tick类和入口函数放在一起,这样你就能把它们复制粘贴到一个文件并运行它们)

 

复制代码
import com.espertech.esper.client.*; import java.util.Random; import java.util.Date;   public class exampleMain {       public static class Tick {         String symbol;         Double price;         Date timeStamp;           public Tick(String s, double p, long t) {             symbol = s;             price = p;             timeStamp = new Date(t);         }         public double getPrice() {return price;}         public String getSymbol() {return symbol;}         public Date getTimeStamp() {return timeStamp;}           @Override         public String toString() {             return "Price: " + price.toString() + " time: " + timeStamp.toString();         }     }       private static Random generator = new Random();       public static void GenerateRandomTick(EPRuntime cepRT) {           double price = (double) generator.nextInt(10);         long timeStamp = System.currentTimeMillis();         String symbol = "AAPL";         Tick tick = new Tick(symbol, price, timeStamp);         System.out.println("Sending tick:" + tick);         cepRT.sendEvent(tick);       }       public static class CEPListener implements UpdateListener {           public void update(EventBean[] newData, EventBean[] oldData) {             System.out.println("Event received: " + newData[0].getUnderlying());         }     }       public static void main(String[] args) {   //The Configuration is meant only as an initialization-time object.
        Configuration cepConfig = new Configuration();         cepConfig.addEventType("StockTick", Tick.class.getName());         EPServiceProvider cep = EPServiceProviderManager.getProvider("myCEPEngine", cepConfig);         EPRuntime cepRT = cep.getEPRuntime();           EPAdministrator cepAdm = cep.getEPAdministrator();         EPStatement cepStatement = cepAdm.createEPL("select * from " +
                "StockTick(symbol='AAPL').win:length(2) " +
                "having avg(price) > 6.0");           cepStatement.addListener(new CEPListener());          // We generate a few ticks...
        for (int i = 0; i < 5; i++) {             GenerateRandomTick(cepRT);         }     } }
复制代码


Output:

 

复制代码
log4j:WARN No appenders could be found for logger (com.espertech.esper.epl.metric.MetricReportingPath). log4j:WARN Please initialize the log4j system properly. Sending tick:Price: 6.0 time: Tue Jul 21 01:11:15 CEST 2009 Sending tick:Price: 0.0 time: Tue Jul 21 01:11:15 CEST 2009 Sending tick:Price: 7.0 time: Tue Jul 21 01:11:15 CEST 2009 Sending tick:Price: 4.0 time: Tue Jul 21 01:11:15 CEST 2009 Sending tick:Price: 9.0 time: Tue Jul 21 01:11:15 CEST 2009 Event received: Price: 9.0 time: Tue Jul 21 01:11:15 CEST 2009
复制代码

 

正如你看到的,只有最后两行数据平均值大于6,因此只有一个事件最终被引擎触发了。相当不错!

 

Oh,你或许担心输出中的第一行,是的,这里还有一点小问题。事实上,Esper使用的日志生成包log4j导致了这个警告。它是可以通过一个叫log4j.xml的文件来配置的,你可以在esper-3.1.0/examples下的某个例子中的/etc目录下找到它。我不认为给我们所有的程序都弄一个xml配置文件是个好主意,所以我们在以下代码中,用点技巧来配置我们的logger,在你的文件开始处加入些import和代码:

 

复制代码
import org.apache.log4j.ConsoleAppender; import org.apache.log4j.SimpleLayout; import org.apache.log4j.Level; import org.apache.log4j.Logger; //and this in the main function before the rest of your code:

public static void main(String [] args){         SimpleLayout layout = new SimpleLayout();       ConsoleAppender appender = new ConsoleAppender(new SimpleLayout());       Logger.getRootLogger().addAppender(appender);       Logger.getRootLogger().setLevel((Level) Level.WARN); (...)
复制代码

 

5分钟到此结束。

 

下一篇文章中,我将更深入一些来探索EPL语句,提供一些代码来连接两个引擎实现所谓的“事件精化”(event refinement)(译者:好像之后作者再也没有更新过了,所以,不要指望后续了:))

 

原文地址:https://coffeeonesugar.wordpress.com/2009/07/21/getting-started-with-esper-in-5-minutes/刘斌华原创翻译,转载请注明出处

 

参考:http://backend.blog.163.com/blog/static/202294126201252114240408

 

窗口处理模式:
    EPL最大的特色就是这个窗口处理模式,有两种窗口,时间窗口和长度窗口。
 
   时间窗口 : 大家想一下,如果有一个场景,要获取最近3秒内OrderEvent的price的平均值,那该怎么做呢?一般的做法需要做个后台线程来做3秒的时间统计,时间到了再做后续处理,虽然不复杂,但是也挺繁琐的。
看看EPL是怎么做的

select avg(price) from test.OrderEvent.win:time(3 sec)

win:time(3 sec)就是定义了3秒的时间窗口,avg(price)就是统计了3秒内的OrderEvent对象的price的平均值
    长度窗口:   长度窗口和时间窗口比较类似

select avg(price) from test.OrderEvent.win:length(100)

win:length(10)就是定义了10个Event的,avg(price)就是统计了最近10个的OrderEvent对象的price的平均值
d) 过滤
where过滤

select avg(price) from test.OrderEvent.win:time_batch(3 sec) where price>10

having过滤

select avg(price) from test.OrderEvent.win:time_batch(3 sec) having price>10

似曾相识啊,执行方式也基本和SQL里的where 和 having差不多。
在EPL里where 是在incoming Events到window之间进行过滤,having是在window到New Eventing之间进行过滤
 
e)聚合
count

select count(price) from test.OrderEvent.win:time_batch(3 sec) where price>10

sum

select sum(price) from test.OrderEvent.win:time_batch(3 sec) where price>10

group by

select itemName,sum(price) from test.OrderEvent.win:time_batch(3 sec) where price>10 group by itemName

都很简单,了解SQL的都狠容易上手
 
f) 函数
ESPER默认加载
java.lang.*
java.math.*
java.text.*
java.util.*
 
支持这些包下的函数方法,例如

select Math.round(sum(price)) from test.OrderEvent.win:time_batch(3 sec) where price>10


它还支持自定义函数,举个例子,做个计算百分比的函数

public class Util {
public static double computePercent(double amount, double total) {
return amount / total * 100;
}
}

配置一下

<plugin-singlerow-function name="percent"
function-class="mycompany.MyUtilityClass" function-method="computePercent" />

OK了,可以用了

select percent(price,total) from OrderEvent

 

阅读全文……

log4j 日志性能优化

日志记录是大多数应用中的一项共同需求。但是日志记录不是没有坏处,我们需要付出性能上的损失。日志是一个I / O活动,并使用'同步'的代码,同时使用appender追加在单一的文件中写入。

 

无法摆脱的日志记录,但要得到最佳的性能,让我们看看,提高我的应用程序性能的最佳做法。

 

1.基本规则 - 通常我们所知道,但很多时间忽略了─

 

如果你正在做在你的日志象mylogString.append(“blah blah”).append(“few more blah blah”).append(“..”),先使用"if(logger.isDebugEnabled())"语句做个判断,如果您的应用程序配置为“Info”或更高的日志记录级别,这样会阻止字符串操作,

堆栈跟踪不应该打印超过一次。

尝试使用SLF4J(包装log4j)。它提供了一些很好的功能开箱。

生产环境设置日志记录级别为“警告”级别(如果可能的话),并以同样的方式设计自己的日志。它有助于保持日志文件体积小,对警告和错误更好的可见性。

2.不要使用“%C”,“%F”,“%M”,“%L”转换字符为的log4j.xml或log4j.properties定义文件日志记录模式。在这里查看官方的警告:

 

http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html(生成调用者的位置信息是极其缓慢的,应该避免,除非执行速度是不是一个问题。)

3.使用日志缓冲 - 缓冲避免发送每个日志事件到日志文件,并会减少IO操作。

 

在下面的示例代码中,使用“Buffered IO” - > true“和BufferSize。然而,在较高的缓冲区大小设置下,要在日志文件中立即看到你的日志(特别是如果你使用“tail -f”看日志文件)有问题。其次,如果服务器崩溃,你可能会看不到仍处于缓冲内存日志记录,还没有刷新日志文件信息的最后一个重要的部分。

<appender name="mainAppender" class="org.apache.log4j.RollingFileAppender">

 <param name="File" value="../log/server.log" />

 <param name="Append" value="true" />

 <param name="MaxFileSize" value="10MB"/>

 <param name="MaxBackupIndex" value="20"/>

 <param name="ImmediateFlush" value="false" />

 <param name="BufferedIO" value="true" />

 <param name="BufferSize" value="16" /> <!-- In KB, Default is 8KB -->

 <layout class="org.apache.log4j.PatternLayout">

        <param name="ConversionPattern" value="%d %X{user}[%t] %-5p - %m%n" />

 </layout>

 </appender>

 

4.考虑使用'Asyn“记录日志 - 使用'ASYN”记录有巨大的性能提升。异步日志记录在后台线程中完成

 

<appender name="asyncAppender" class="org.apache.log4j.AsyncAppender"> 

 <param name="BufferSize" value="128" />

 <param name="Blocking" value="true" />

 <appender-ref ref="mainAppender" /> 

 </appender>

 

在上面的代码中三个重要的参数-

 

缓冲区大小:这又是一个需要在“性能”和“延时刷新日志数据'之间权衡。大的值有较高的的性能。

阻塞:意思是'如果缓冲区已满,仍在等待刷新,怎么做“。如果是“false”,日志appender将丢弃记录事件,直到缓冲区可用,提供了更好的性能。默认为“true”。这也是一个权衡。然而,'Asyn'的日志操作可以忽略main appender缓存的日志事件。

Appender-Ref:它采用最初定义“main Appender”(步骤2)做记录。然而,在“ASYN”记录场景,您可以忽略main appender缓冲日志事件的。请注意,“ASYN logger”缓冲基于“日志事件数量'但是'main logger”缓冲是基于日志的大小。

此外,更多的缓冲 ->刷新日志有更多的延迟​​。 (你可能错过服务器崩溃情况下的一些日志)

 

5.映射诊断上下文(MDC) - 虽然这个功能和性能没有直接关系的,但它让你的日志代码干净。 MDC使用ThreadLocal的存储来存储上下文方面的具体信息。 MDC可以用于记录像loged-in的用户名,事务ID,任何其它标识符来记录日志。

 

这真的有助于调试用户或交易的日志。这可通过跨类和方法的传递上下文的具体信息。

 

In your code - > MDC.put("userssion.getUser()); (import org.apache.log4j.MDC)

In Log4j.xml (or log4j.properties), set pattern layout with %X: 

 <param name="ConversionPattern" value="%d %X{user}[%t] %-5p - %m%n" />

 

2012-11-02 12:12:04,586 [user: 0x3c5c84b3][pool-7-thread-182] DEBUG .......

2012-11-02 12:12:04,586 [user: 0x4c678c66][pool-7-thread-182] DEBUG .......

 

6.多个记录器支持 - 如果在你的应用程序使用多个记录器,如果你得到重复的日志条目,请考虑加上'additivity=talse'属性。

<logger name="me.prettyprint" additivity="false">

 <level value="error" />

 <appender-ref ref="cassandraAppender" />

</logger>

 

7.可以考虑压缩日志文件,如果担心存储和文件传输。

<appender...>

 <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">

 <param name="FileNamePattern" value="server.log.%d{yyyy-MM}**.gz**"/>

 </rollingPolicy>

</appender>

http://logging.apache.org/log4j/companions/extras/apidocs/org/apache/log4j/rolling/TimeBasedRollingPolicy.html

 

8.有时需要服务器切换到不同的日志级别(如“警告”,以“调试”),但不希望修改服务器。我们可以通过编程配置我们的日志级别,公开为JMX服务:

public void setLogLevel(String loggerName, String newLevel) throws IllegalArgumentException {

 final Level level = Level.toLevel(newLevel);

 if (!newLevel.equalsIgnoreCase(level.toString())) {

 throw new IllegalArgumentException("Invalid log level");

 }

final Logger logger = Logger.getLogger(loggerName);

 if ((logger == null) || (logger.getLevel() == null)) {

 throw new IllegalArgumentException("Logger does not exist."); 

 }

logger.setLevel(level);

}

9.可以改变日志到ASYN/ SYN,缓冲/非缓冲模式。欲了解更多详情请参阅log4j的API。为了演示,我给一个片段:

// Get All Appenders 

 Enumeration<Appender> appenders = LogManager.getRootLogger().getAllAppenders();

 // Get your Appender from Enumeration, 

 if (appender instanceof AsyncAppender) {

 // Get all internal Appenders user by Asyn Appender

 Enumeration<Appender> mainAppenders = asyncAppender.getAllAppenders();

 // Get Main Appender, and do your operations

 mainFileAppender.setImmediateFlush(false);

 mainFileAppender.setBufferedIO(false);

 

标签 :