GithubHelp home page GithubHelp logo

zhegexiaohuozi / seimicrawler Goto Github PK

View Code? Open in Web Editor NEW
2.0K 175.0 679.0 464 KB

一个简单、敏捷、分布式的支持SpringBoot的Java爬虫框架;An agile, distributed crawler framework.

Home Page: http://seimicrawler.org

License: Apache License 2.0

Java 100.00%

seimicrawler's Introduction

SeimiCrawler

GitHub release Maven License

An agile,powerful,standalone,distributed crawler framework.Support spring boot and redisson.

SeimiCrawler的目标是成为Java里最实用的爬虫框架,大家一起加油。

简介

SeimiCrawler是一个敏捷的,独立部署的,支持分布式的Java爬虫框架,希望能在最大程度上降低新手开发一个可用性高且性能不差的爬虫系统的门槛,以及提升开发爬虫系统的开发效率。在SeimiCrawler的世界里,绝大多数人只需关心去写抓取的业务逻辑就够了,其余的Seimi帮你搞定。设计**上SeimiCrawler受Python的爬虫框架Scrapy启发,同时融合了Java语言本身特点与Spring的特性,并希望在国内更方便且普遍的使用更有效率的XPath解析HTML,所以SeimiCrawler默认的HTML解析器是JsoupXpath(独立扩展项目,非jsoup自带),默认解析提取HTML数据工作均使用XPath来完成(当然,数据处理亦可以自行选择其他解析器)。并结合SeimiAgent彻底完美解决复杂动态页面渲染抓取问题。

最新进展、资讯订阅

  • 微信订阅号

weixin

里面会发布一些使用案例等文章,以及seimi体系相关项目的最新更新动态,后端技术,研发感悟等等。

V2.0版本新特性

  • 完美支持SpringBoot,demo参考

  • 回调函数支持方法引用,设置起来更自然

    push(Request.build(s.toString(),Basic::getTitle));

  • 非SpringBoot模式全局配置项通过SeimiConfig进行配置,包括 Redis集群信息,SeimiAgent信息等,SpringBoot模式则通过SpringBoot标准模式配置
SeimiConfig config = new SeimiConfig();
config.setSeimiAgentHost("127.0.0.1");
//config.redisSingleServer().setAddress("redis://127.0.0.1:6379");
Seimi s = new Seimi(config);
s.goRun("basic");

SpringBoot模式,在application.properties中配置

seimi.crawler.enabled=true
# 指定要发起start请求的crawler的name
seimi.crawler.names=basic,test

seimi.crawler.seimi-agent-host=xx
seimi.crawler.seimi-agent-port=xx

#开启分布式队列
seimi.crawler.enable-redisson-queue=true
#自定义bloomFilter预期插入次数,不设置用默认值 ()
#seimi.crawler.bloom-filter-expected-insertions=
#自定义bloomFilter预期的错误率,0.001为1000个允许有一个判断错误的。不设置用默认值(0.001)
#seimi.crawler.bloom-filter-false-probability=
  • 分布式队列改用Redisson实现,底层依旧为redis,去重引入BloomFilter以提高空间利用率,一个线上的BloomFilter调参模拟器地址

  • JDK要求 1.8+

原理示例

基本原理

SeimiCrawler原理图

集群原理

SeimiCrawler集群原理图

社区沟通讨论

  • QQ群:557410934

QQ群

这个就是给大家自由沟通啦

快速开始

添加maven依赖(以**maven库最新版本为准,下仅供参考):

<dependency>
    <groupId>cn.wanghaomiao</groupId>
    <artifactId>SeimiCrawler</artifactId>
    <version>2.1.4</version>
</dependency>

在包crawlers下添加爬虫规则,例如:

@Crawler(name = "basic")
public class Basic extends BaseSeimiCrawler {
    @Override
    public String[] startUrls() {
        return new String[]{"http://www.cnblogs.com/"};
    }
     @Override
     public void start(Response response) {
         JXDocument doc = response.document();
         try {
             List<Object> urls = doc.sel("//a[@class='titlelnk']/@href");
             logger.info("{}", urls.size());
             for (Object s:urls){
                 push(Request.build(s.toString(),Basic::getTitle));
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
     public void getTitle(Response response){
         JXDocument doc = response.document();
         try {
             logger.info("url:{} {}", response.getUrl(), doc.sel("//h1[@class='postTitle']/a/text()|//a[@id='cb_post_title_url']/text()"));
             //do something
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
}

然后随便某个包下添加启动Main函数,启动SeimiCrawler:

public class Boot {
    public static void main(String[] args){
        Seimi s = new Seimi();
        s.start("basic");
    }
}

以上便是一个最简单的爬虫系统开发流程。

工程化打包部署

Spring boot(推荐)

推荐使用spring boot方式来构建项目,这样能借助现有的spring boot生态扩展出很多意想不到的玩法。Spring boot项目打包参考spring boot官网的标准打包方式即可

mvn package

独立运行

上面可以方便的用来开发或是调试,当然也可以成为生产环境下一种启动方式。但是,为了便于工程化部署与分发,SeimiCrawler提供了专门的打包插件用来对SeimiCrawler工程进行打包,打好的包可以直接分发部署运行了。

pom中添加添加plugin

<plugin>
    <groupId>cn.wanghaomiao</groupId>
    <artifactId>maven-seimicrawler-plugin</artifactId>
    <version>1.2.0</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
    <!--<configuration>-->
        <!-- 默认target目录 -->
        <!--<outputDirectory>/some/path</outputDirectory>-->
    <!--</configuration>-->
</plugin>

执行mvn clean package即可,打好包目录结构如下:

.
├── bin             # 相应的脚本中也有具体启动参数说明介绍,在此不再敖述
│   ├── run.bat    #windows下启动脚本
│   └── run.sh     #Linux下启动脚本
└── seimi
    ├── classes     #Crawler工程业务类及相关配置文件目录
    └── lib         #工程依赖包目录

接下来就可以直接用来分发与部署了。

详细请继续参阅maven-seimicrawler-plugin

更多文档

目前可以参考demo工程中的样例,基本包含了主要的特性用法。更为细致的文档移步SeimiCrawler主页中进一步查看

Change log

请参阅 ChangeLog.md

项目源码

Github

BTW: 如果您觉着这个项目不错,到github上star一下,我是不介意的 ^_^

seimicrawler's People

Contributors

54uso avatar dreamerdream avatar eukway avatar zhegexiaohuozi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

seimicrawler's Issues

开发须知 第一版

  • 为了更好的管理代码,目前仓库分两个分支,devmaster,其中dev分支约定为开发分支,所有贡献者在fork后需在Dev分支提交代码,然后提交PR,然后合并至主库的dev分支,再由管理员统一merge到master分支做最终部署以及相关调整。

  • 其他待定

Socket代理问题

@crawler里配置了socket代理 proxy = "socket://127.0.0.1:10808",实际执行时确使用的http代理,查看源码发现实现似乎不对:
image

试了一下DefaultRedisQueueEG

例子运行完后,控制台输出20:56:50 INFO c.w.crawlers.DefaultRedisQueueEG - url:http://www.cnblogs.com/yzg1/p/5010789.html [Backbone学习笔记一Backbone中的MVC,。。。。
说明爬取例子执行完毕,过了大概不到1分钟后,redis服务端报了下列错误,有大量的客户端发起链接请求,
4364] 01 Dec 20:57:19 - Accepted 127.0.0.1:3160
4364] 01 Dec 20:57:19 - DB 0: 2 keys (0 volatile) in 4 slots HT.
4364] 01 Dec 20:57:19 - 560 clients connected (0 slaves), 5069156 bytes in us,
后停止redis服务,控制台输出下列错误
20:57:21 WARN c.w.seimi.def.DefaultRedisQueue - java.net.SocketException: Connection reset
20:57:21 INFO c.w.seimi.def.DefaultRedisQueue - create redisPool host=127.0.0.1,port=6379
说明在爬虫执行完毕后,系统自动发出了大量链接请求,造成redis崩溃了,请问是我的配置问题还是系统例子的问题

使用代理过程中报错

使用代理过程中出现了与问题37出现的问题一样 #37

java.lang.ClassCastException: org.apache.http.message.BasicHttpRequest cannot be cast to org.apache.http.client.methods.HttpUriRequest
at cn.wanghaomiao.seimi.http.hc.HcDownloader.getRealUrl(HcDownloader.java:180) ~[SeimiCrawler-2.0.jar:na]
at cn.wanghaomiao.seimi.http.hc.HcDownloader.renderResponse(HcDownloader.java:117) ~[SeimiCrawler-2.0.jar:na]
at cn.wanghaomiao.seimi.http.hc.HcDownloader.process(HcDownloader.java:79) ~[SeimiCrawler-2.0.jar:na]
at cn.wanghaomiao.seimi.core.SeimiProcessor.run(SeimiProcessor.java:101) ~[SeimiCrawler-2.0.jar:na]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_181]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]

但是在37问题中看到说使用okHttp就可以了,不使用默认的HttpClient,我在文档中也找到了使用OkHttp的办法,官方文档关于使用Okhttp链接。

更换为 okHttp之后上述问题的确没有了,但是出现了另一个问题,就是使用代理访问的返回结果response里面的content内容出现乱码,暂时不知如何解决。求大神解答!!!

作者,用你那个DefaultRedisQueue做队列,好像并不能执行JS进行渲染。

我爬取需要JS渲染的网站,用defaultlocalqueue是能正确的将渲染完的网页给我的,但是我用你那个DefaultRedisQueue去做队列,返回给我的document好像是没有经过js渲染的数据,类似于json格式的数据返回给我了。
例子:
@crawler(name = "jdcrawlers",httpTimeOut = 30000,queue = DefaultRedisQueue.class)
Request request = new Request();
request.setUrl(URL);
request.useSeimiAgent();
request.setSeimiAgentRenderTime(5000);
request.setSeimiAgentUseCookie(true);
request.setSeimiAgentScript("$(function(){$(document).scrollTop($(document).height()-$(window).height());});");
request.setCallBack("jingdong");
requests.add(request);
JXDocument document = response.document();
我看brpop出来的request任然是原来那个request,但是提交到seimiagent之后返回document的是类似于json格式的数据,并不是html网页,我看你那个demo爬的是csdn.blog,他是没有经过js渲染的,所以它返回的任然是html,并不json格式的数据

SeimiDownloader 处理 uri 存在问题

当匹配到这样的url的时候 //fundact.eastmoney.com/banner/Hot_Em.html?spm=xlb

public Response metaRefresh(String nextUrl) throws Exception {
if (!nextUrl.startsWith("http")) {
String prefix = this.getRealUrl(this.httpContext);
nextUrl = prefix + nextUrl;
}

    this.logger.info("Seimi refresh url to={} from={}", nextUrl, this.currentReqBuilder.getUri());
    this.currentReqBuilder.setUri(nextUrl);
    this.httpResponse = this.hc.execute(this.currentReqBuilder.build(), this.httpContext);
    return this.renderResponse(this.httpResponse, this.currentRequest, this.httpContext);
}

这里的拼接就有问题了

关于注解使用

       Seimi s = new Seimi();
         s.scan("com");

这个scan方法是设置扫描某个包下带有Crawler注解的意思吗?为什么我配置成com还是报
13:39:06 ERROR cn.wanghaomiao.seimi.core.Seimi - can not find any crawlers,please check!
难道只能扫描包名称是cn.wanghaomiao的包下的注解类吗?

代理问题

当使用OKhttp时,一个爬虫使用代理另一个爬虫不使用代理,不使用代理的爬虫仍然会使用代理。

原因:
问题根源在于OkHttpClientBuilderBox这个类设计不合理,所有爬虫处理时共用一个OkHttpClient.Builder。设置代理后的builder会被污染。

解决方案:
建议取消单例模式,不共用同一个builder

Request 类中的 meta 能否变成 Map<String, Object>

当我需要穿一个Long类型的值的时候,原来的 Map<String, String> 取值非常不方便,放值取值的时候都需要分别转换一次,而且这里看到并没特别处理,能否变量类型就直接改成 Map<String, Object>

添加多账号支持,和单账号链路追踪。

特性请求:1. 多账号支持;2. 基于账号的链路追踪。3. 增加插件机制。如,智能识别 403limit,动态算法调整速率,并且记忆保存。

业务场景

使用task,使用多个账号,登录。获取cookie进行保存。

在其他task中,批量的,获取cookie进行访问。

在访问过程中,一个cookie的,请求路径和账号绑定,使得可视化日志。

请问一下mater分支中农SpringBoot demo

first:indexController中第二个方法是什么意思呢?
second: 貌似直接启动就会运行Crawlers下的start方法吗?这个感觉有点不太好吧~希望可以借用用定时器还是其他主动调用方式 我在您写的文档中没有看到相关的描述

复杂的动态html页面,比如执行js

你这个只能抓取静态的html吗?复杂的比如extjs框架产生的html页面怎么抓取吗?ajax请求可能会请求几秒,然后才渲染出页面,这个怎么设置吗?还是说考虑到啊

【通知】此项目以后的任何问题讨论仅接受中文

  • 实在是看不惯某些狗腿子项目被apache孵化后就要求开发者必须用中文提Issue,代表性的就是skywalkingshenyu
  • 他们的理由可能很多,但都特么是借口
  • 我们可以国际化,中英双语本来也没什么问题,但为啥要歧视中文?
  • 以上仅代表个人观点和我个人可以管理的项目,能做的也就这么点了

有块代码逻辑想请教一下作者

image

如上图,SeimiCrawlerBootstrapListener中的line74,在这个嵌套循环中,请问一下为什么要多个线程来执行同一个crawler?在SemiProcessor中,其中一个线程优先执行完queue.bPop(crawlerModel.getCrawlerName())这句后,其它线程不就一直死循环下去吗?请问为什么这么设计呢?

关于带cookie访问的问题

你好,目前我发现框架是不支持设置请求的cookie(直接在浏览器中复制出来)然后直接访问的URL的,我想问一下如果想要添加这个功能该怎么改源码?望指点一下。谢谢!!!

Invalid cookie header: "Set-Cookie:

问题:
CookieStore cookieStore = httpClient.getCookieStore();
这种形式获取cookie可能会导致cookie信息获取不全,报invalid cookie header httpclient警告提
示;
提示信息如下
o.a.h.c.protocol.ResponseProcessCookies : Invalid cookie header: "Set-Cookie: activityAccountJumpPage=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=kuaishou.com". Invalid 'expires' attribute: Thu, 01 Jan 1970 00:00:00 GMT

解决方法:
Response response = httpClient.execute(httpPost);
response.getHeaders("Set-Cookie")[0].getValue();

求大佬更新完善

非常喜欢这个框架 ,已经用了好多次了。 希望大佬不要停更,继续完善

用官网的demo加上本地的127.0.0.1:1080代理就报错

@Crawler(name = "basic",proxy = "http://127.0.0.1:1080")
public class Basic extends BaseSeimiCrawler {

    @Override
    public String[] startUrls() {
        //两个是测试去重的
        return new String[]{"http://www.cnblogs.com/","http://www.cnblogs.com/"};
    }

    @Override
    public void start(Response response) {
        JXDocument doc = response.document();
        try {
            List<Object> urls = doc.sel("//a[@class='titlelnk']/@href");
            logger.info("{}", urls.size());
            for (Object s:urls){
                push(Request.build(s.toString(),Basic::getTitle));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void getTitle(Response response){
        JXDocument doc = response.document();
        try {
            logger.info("url:{} {}", response.getUrl(), doc.sel("//h1[@class='postTitle']/a/text()|//a[@id='cb_post_title_url']/text()"));
            //do something
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
2018-12-14 17:35:00.209 ERROR 12784 --- [ool-2-thread-17] c.wanghaomiao.seimi.core.SeimiProcessor  : org.apache.http.message.BasicHttpRequest cannot be cast to org.apache.http.client.methods.HttpUriRequest

java.lang.ClassCastException: org.apache.http.message.BasicHttpRequest cannot be cast to org.apache.http.client.methods.HttpUriRequest
	at cn.wanghaomiao.seimi.http.hc.HcDownloader.getRealUrl(HcDownloader.java:180) ~[SeimiCrawler-2.0.jar:na]
	at cn.wanghaomiao.seimi.http.hc.HcDownloader.renderResponse(HcDownloader.java:117) ~[SeimiCrawler-2.0.jar:na]
	at cn.wanghaomiao.seimi.http.hc.HcDownloader.process(HcDownloader.java:79) ~[SeimiCrawler-2.0.jar:na]
	at cn.wanghaomiao.seimi.core.SeimiProcessor.run(SeimiProcessor.java:101) ~[SeimiCrawler-2.0.jar:na]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_181]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_181]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]

运行 demo 得到404

我运行这个项目的 demo 中的 jdWalker ,把账号密码改成我的,结果得到的 response.getContent() 是

{
"timestamp":1551353888182,
"status":404,
"error":"Not Found",
"message":"No message available",
"path":"/doload"
}

这是什么问题?

好吧,那是我没启动SeimiAgent 造成的,不过,即便是启动了,得到的,也是未登录之前的页面,所以,目前还达不成我想爬取登录后的页面内容的目的,我再研究下吧

关于登录后cookie状态延续的问题

你好,我使用useCookie=true,登录状态只延续到一个请求,如果我多发起几个请求,则cookie状态失效,不知道为什么,可否能够手工传递这个cookie值呢?代码如下:

public void getMainInfo(Response response)
{

     for (int i = 1; i < 5; i++)
     {

        Request productListInfo = Request.build("http://www.interotc.cn/mall/gtlmcp_list.co?pageSize=4000&pageNo="+i+"&count=4683&XXLY=&mainid=gtlmcp_img_list_container_id&showPageBar=true&CPFL=&HEAD=&pageIndex=1", "getProductMainInfo").setSeimiAgentUseCookie(true);
        push(productListInfo);

     }

}

我用这个循环去做请求,则cookies失效

关于获取爬虫进度的问题

使用CrawlerCache.getCrawlerModel(name)方法只能获取Crawler所有入队的请求数和剩余长度,无法获取我当前的进度,假如我第一次抓取发了20个请求,第二次抓取发了20个请求那个total就是40了就没法计算第二次抓取的进度了

日志级别

这个日志级别怎么改啊?好像在配置文件里面改不生效,有没有大神解答一下

使用代理ip采集豆瓣时候报错

使用代理ip采集豆瓣读书时候报错,但是采集非https的网站时候不报错。报错如下:
java.lang.ClassCastException: org.apache.http.message.BasicHttpRequest cannot be cast to org.apache.http.client.methods.HttpUriRequest
at cn.wanghaomiao.seimi.http.hc.HcDownloader.getRealUrl(HcDownloader.java:180) ~[SeimiCrawler-1.3.1.jar:na]
at cn.wanghaomiao.seimi.http.hc.HcDownloader.renderResponse(HcDownloader.java:117) ~[SeimiCrawler-1.3.1.jar:na]
at cn.wanghaomiao.seimi.http.hc.HcDownloader.process(HcDownloader.java:79) ~[SeimiCrawler-1.3.1.jar:na]
at cn.wanghaomiao.seimi.core.SeimiProcessor.run(SeimiProcessor.java:106) ~[SeimiCrawler-1.3.1.jar:na]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_60]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_60]

Cookie rejected

您好,我最近使用您的Crawler爬百度新闻,遇到了这个问题,我查了相关的资料,这个问题需要自己写个Cookie Policy,请问我应该改哪个档案呢?
Cookie rejected [x-logic-no="4", version:0, domain:.mbd.baidu.com, path:/, expiry:null] Illegal domain attribute "mbd.baidu.com". Domain of origin: "baijiahao.baidu.com"

Response.ren de r

java.lang.IllegalAccessException: Class cn.wanghaomiao.seimi.core.SeimiBeanResolver can not access a member of class app.model.Activity with modifiers ""
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Field.set(Field.java:761)
at cn.wanghaomiao.seimi.core.SeimiBeanResolver.parse(SeimiBeanResolver.java:56)
at cn.wanghaomiao.seimi.struct.Response.render(Response.java:156)
at app.crawlers.Basic.start(Basic.java:61)

您好,请问要传递cookie,需要怎么传递的呢?

我用下面的这种方式传,是无效的。
List seimiCookies = new ArrayList();
SeimiCookie s = null;
s = new SeimiCookie("xxx", "/", "xxx", "xxxx");
seimiCookies.add(s);
push(Request.build("xxxxxxxxxxxxx", "getQus")
.setSeimiAgentUseCookie(true)
.setSeimiAgentRenderTime(6000)
.setSeimiAgentContentType(SeimiAgentContentType.HTML)
.setSeimiCookies(seimiCookies));

V2.0.0开发计划

  • 深度支持Spring boot,方便开发者进行更为有意思的尝试

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.