汪建军的博客

休迅飞凫,飘忽若神,凌波微步,罗袜生尘。

Android developer, sometimes thinking, sometimes try it.


一个简单而完整的后台开发实例

前阵子尝试了一下抓取所有金山词霸每日一句的数据,存到数据库,然后自己写接口放到云主机上调用,运气不错,一切顺利,下面说说我是怎么做的。

打开金山词霸某一天的每日一句,比如这个:http://news.iciba.com/dailysentence/detail-500.html#nav ,右键-查看源代码,数据非常规律,url也是顺次增长的,比较好拿。

分析html代码,java上有个工具特别好用,叫jsoup,不熟悉的小伙伴请认真学习一下这个


基础配置见我之前写的一篇

新建一个Spring工程,初始会有两个类:ServletInitializerXXXApplication初始化和入口类,暂时不用管,也不需要修改他们。可以如下建包:

"model"包下新建一个bean,命名为:IcibaDaily。 我习惯用fastjson构建实体类,下面的字段按喜好增删。

import com.alibaba.fastjson.annotation.JSONField;
import javax.persistence.*;
/**
 * Created by Wang on 2015/12/25.
 */
@Entity
@Table(name = "t_iciba_daily")
public class IcibaDaily {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

private long no;

private String date;
@JSONField(name = "dailychinese")
@Column(name = "daily_chinese")
private String dailyChinese;
@JSONField(name = "dailyenglish")
@Column(name = "daily_english")
private String dailyEnglish;
@JSONField(name = "dailyeditor")
@Column(name = "daily_editor")
private String dailyEditor;

@JSONField(name = "mp3url")
@Column(name = "mp3_url")
private String mp3Url;

@JSONField(name = "readingscan")
@Column(name = "reading_scan")
private String readingScan; // 浏览次数
@JSONField(name = "readinglike")
@Column(name = "reading_like")
private String readingLike;
@JSONField(name = "readingcollect")
@Column(name = "reading_collect")
private String readingCollect;
@JSONField(name = "readingreview")
@Column(name = "reading_review")
private String readingReview;

public IcibaDaily(){}

public IcibaDaily(long no, String date, String dailyChinese, String dailyEnglish, String dailyEditor, String mp3Url
        , String readingScan, String readingLike, String readingCollect, String readingReview) {
    this.no = no;
    this.date = date;
    this.dailyChinese = dailyChinese;
    this.dailyEnglish = dailyEnglish;
    this.dailyEditor = dailyEditor;
    this.mp3Url = mp3Url;
    this.readingScan = readingScan;
    this.readingLike = readingLike;
    this.readingCollect = readingCollect;
    this.readingReview = readingReview;
}
 ...
这里是所有属性的get和set方法,用fastjson的话不能省略。
}

说明:

类名上添加的两行

@Entity
@Table(name = "t_iciba_daily")

这是JPA的写法,点进Entity可以发现来自hibernate-jpa下显然,这表示在数据库里新建一个表,表字段就是这个类的每一个字段。字段上可以注解一些属性: 如id字段上:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

这表示将id设置为主键并自增长。

表里的列名默认就是字段的名称,你可以通过如:

@Column(name = "daily_chinese")
private String dailyChinese;

改为自定义列名。


"repository"包下新建一个仓库,命名为:IcibaDailyRepository。

import org.springframework.data.jpa.repository.JpaRepository;
/**
 * Created by Wang on 2015/12/25.
 */
public interface IcibaDailyRepository extends JpaRepository<IcibaDaily, Long> {
    IcibaDaily findIcibaDailyByDate(String date);
}

按照JPA命名规则随意加上一些增删改查的语句,规则网上有很多教程,比如上面显然就是通过date查找一条数据,也可以通过方法上添加@Query("sql 语句")手写sql查询。


"contrallor"包下新建一个实现类,命名为:RunIciba。

import demo.model.IcibaDaily;
import demo.repository.IcibaDailyRepository;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static demo.contrallor.Utils.getDoc;
/**
 * Created by Wang on 2015/12/25.
 */
@RestController
@RequestMapping(produces = "application/json;charset=UTF-8")
public class RunIciba {
@Autowired
IcibaDailyRepository icibaDailyRepository;

long id;
long index = 1; // 循环次数统计
int emptyNum; // 连续为空次数超过100次跳出循环,结束抓取

@RequestMapping(value = "/runiciba")
public String runiciba(){
    new Thread(() -> {
        while (true) {
            Document doc = getDoc("http://news.iciba.com/dailysentence/detail-" + index + ".html#nav");
            String date = null;
            String dailyChinese = null;
            String dailyEnglish = null;
            String dailyEditor = null;
            String mp3Url = null;
            String readingScan = null; // 浏览次数
            String readingLike = null;
            String readingCollect = null;
            String readingReview = null;
            if (doc != null) {
                date = doc.select("div.r_hd").select("span").text();
                dailyChinese = doc.select("div.reading_txt").select("li.cn").text();
                dailyEnglish = doc.select("div.reading_txt").select("li.en").text();
                dailyEditor = doc.select("div.reading_txt").select("li.editor").text();
                mp3Url = doc.select("audio").select("source").attr("src");
                readingScan = doc.select("li.reading_scan").text();
                readingLike = doc.select("li.reading_like").text();
                readingCollect = doc.select("li.reading_collect").text();
                readingReview = doc.select("li.reading_review").text();

                date = date==null?"":date;
                dailyChinese = dailyChinese==null?"":dailyChinese;
                dailyEnglish = dailyEnglish==null?"":dailyEnglish;
                dailyEditor = dailyEditor==null?"":dailyEditor;
                mp3Url = mp3Url==null?"":mp3Url;
                readingScan = readingScan==null?"":readingScan;
                readingLike = readingLike==null?"":readingLike;
                readingCollect = readingCollect==null?"":readingCollect;
                readingReview = readingReview==null?"":readingReview;

                if(date.length() > 0 && dailyEnglish.length() > 0 && icibaDailyRepository.findIcibaDailyByDate(date)==null) {
                    emptyNum = 0;
                    IcibaDaily icibaDaily = new IcibaDaily(index, date, dailyChinese, dailyEnglish, dailyEditor,
                            mp3Url, readingScan, readingLike, readingCollect, readingReview);
                    IcibaDaily daily = icibaDailyRepository.save(icibaDaily);
                    if(daily!=null) {
                        System.out.println("save successful, ID=" + id + ",no=" + daily.getNo() + ",date=" + daily.getDate() + ",mp3Url="+mp3Url);
                        id++;
                        index++;
                    }
                } else {
                    index++;
                }
            } else {
                index++; // index自增的情况:内容有误 or 内容正确且保存完
            }
            if(emptyNum > 100) {
                System.out.println("抓取完成!");
                return;
            }
            emptyNum++;
        }
    }).start();
    return "running...";
}
}

这里实现了这样一个逻辑: 观察金山词霸每日一句的url会发现它里面有个自增长的规律,于是我从index=0开始,读取网页内容,抓取想要的数据(具体上面的实现相信你看完jsoup教程会很容易理解)存入数据库,抓完之后index++,当index连续增长了100次都没有抓到数据说明抓完了,结束。

代码说明: 类名上的两行

@RestController
@RequestMapping(produces = "application/json;charset=UTF-8")

表示这个类是一个REST服务的控制器servlet且返回json数据,格式为utf-8。

@Autowired

这是一个典型的Spring 依赖注入(ioc),我们依赖IcibaDailyRepository,但是无需我们自己初始化,直接拿Spring给我们创建好的就行了。

@RequestMapping(value = "/runiciba")

这个注解表示指定该方法为接口。value就是接口名。默认是get请求,post请求的话加上method = RequestMethod.POST,如@RequestMapping(value = "/test", method = RequestMethod.POST)
参数名称作为接口的传参名,如public String test(String user,String pwd) {...}到时候接口传参,有两个参数,key分别为user和pwd。
new Thread(() -> {...});这是java8新引入的lambda表达式写法,和

new Thread(new Runnable() {
        @Override
        public void run() {
            ...
        }
    });

是一个意思,只是简化了一下。

icibaDailyRepository.save(icibaDaily);

这一句就是入数据库。

return "running...";

最后一句返回的字符串会打印到网页上,不重要。
差点忘了还有个方法:

public static Document getDoc(String url) {
    try {
        return Jsoup
                .connect(url)
                .timeout(40 * 1000)
                .userAgent(
                        "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)")
                .get();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

这是得到一个Jsoup的Document对象。


全部代码都在上面了,按照我上一篇博客的配置好tomcat和application.properties,Mysql也别忘了跑起来(这里说的是云主机上的Mysql,IDEA连的也是云主机上的Mysql),然后运行程序。会自动弹出:http://localhost:8080/将url拼上你定义的接口名,如上面的就是这样http://localhost:8080/runiciba,回车,网页上只会出现你定义的一个返回字符串,如上面的running...,然后你可以在控制台慢慢看结果。



跑完之后就可以来写接口了!!

"contrallor"包下新建一个类,我将它命名为:ContrallorDemo。

@RestController
@RequestMapping(produces = "application/json;charset=UTF-8")
public class ContrallorDemo {

@Autowired
IcibaDailyRepository icibaDailyRepository;

@RequestMapping(value = "iciba")
public String test() {
    IcibaDaily icibaDaily = icibaDailyRepository.findOne(1l);
    return JSON.toJSONString(icibaDaily);
}

@RequestMapping(value = "idaily")
public String icibaDailyList(int p) {
    Page<IcibaDaily> page = icibaDailyRepository.findAll(new PageRequest(p,20));
    return JSON.toJSONString(page.getContent());
}
}

这里随意给两个示意接口:

第一个接口iciba是返回数据库里ID=1的这条并解析成json串,这里传的参数和你定义的IcibaDailyRepository的ID类型 都要 和IcibaDaily实体类定义的ID保持一致,Integer就传int类型,Long就传long类型。请求实例:
你云主机的ip或设置的子域名/iciba

第二个接口idaily分页返回一组(20条)数据并解析成json串,具体返回哪一组,通过传的参数p来决定。这就是做前端的小伙伴非常熟悉的列表格式,size、nextpage、totalpage都可以通过Spring的Page类来获得或者算出来。这里缺省为get,请求实例:
你云主机的ip或设置的子域名/idaily?p=1


如何部署到云主机上,上一篇博客也有说明,玩得开心,不清楚的欢迎留言,没说对的感谢指点!^O^

转载出处:http://zzimoo.com/simple/

最近的文章

及时同步ss密码

我喜欢用shadowsocks当梯子,前阵子发现有个网站能提供免费的ss账号密码,但是这个密码每6小时更换一次(它是这么说,但是不一定,有时候可能很快就换了),维仔说这么麻烦!于是我动手写了个让更换密…

举个例子继续阅读
更早的文章

如何用Spring MVC + JPA + Mysql + Tomcat + Nginx开发服务端

新入手的云主机,域名也刚备案通过,当然不单单为了搭建一个博客系统,更多的是想接触一下后台开发。在大湿兄的指点下动手写了一个简单的接口,运气不错,能跑起来,下面说下我是怎么做的。 关于SpringMVC…

后端继续阅读
comments powered by Disqus