java实现定时刷新网页、解析网页、获取指定内容、发邮件

懒惰的人总有方法不让自己去做简单、重复的事情。

缘由

今天是3月10号,是绍兴市上虞区教育体育局发布2017年绍兴市上虞区中小学幼儿园教师公开招聘的现场确认通知的时候。早上开始,女朋友就开始关注到底什么时候会发布这个通知。那传统的方式就是打开网站,查看通知通告到底有没有发送,但是作为一个程序员,感觉这事情有点简单、重复。那么作为一个懒人,就开始着手设计一套自己能跑起来的程序,来代替自己做事。

想要实现的功能

  1. 定时访问网页
  2. 获取网页内容解析
  3. 判断是否已发送公告,实现发送邮件

实现的效果图

效果图.jpg

着手实施

1.定时访问网页

  • 定时
    此处使用简单的while(true),作为死循环,然后通过Thread.sleep()来实现定时效果。所以,一开始的代码是这样的:
public static void main(String[] args) {
    while (true) {
        System.out.println("啦啦啦");
        Thread.sleep(1000);
    }
}
public class HttpUtil {

    /**
     * 向指定URL发送GET方法的请求
     * 
     * @param url
     *            发送请求的URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 向指定 URL 发送POST方法的请求
     * 
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常!"+e);
            e.printStackTrace();
        }
        //使用finally块来关闭输出流、输入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }    
}
  • 定时访问网页
    现在,他的代码是这样的。
public static void main(String[] args) throws InterruptedException{
        String url = "http://www.syjy.cn/moreInfo.aspx?layoutTemplateId=135&bigClassPkId=435";
        while (true) {
            HttpUtil.sendPost(url, null);
            Thread.sleep(1000);
        }
    }
  • 小插曲
    由于访问频率过高,一秒就访问了一次。后面发现访问失败了,一开始以为是访问频率过高,把我IP给封了。后来打开网页出现的是这个页面。
    服务器错误
    Are you kidding me?我就一秒一次。。你就挂了。后来一想,我怎么感觉我在犯罪呢。。还好,后来网站又可以正常访问了。虚惊一场。然后就把访问频率改成了60秒一次,因为也不需要那么频繁去访问。

2.解析网页内容,获取重要线索

  • 工具准备
    解析网页,我用到的是jsoup-1.7.1.jar
  • 分析网页内容
    获取网页内容后,找到了公告列表的html内容,如下图。
    公告内容
    通过分析,可以看到这个div<div class="nsy-news"></div>包裹了所有的公告内容,然后分析每一条公告。每一条公告是通过一个 <li></li> 标签。而 <li></li> 是由 <img></img> <a></a> <p></p> 三个标签组成。我们需要的属性是<a></a>里面的链接地址 href和标题名称,还有<p></p>标签里的时间。
    现在可以新建一个User.java,包含 title , url , time 三个属性。
public class User {
    private String title;
    private String time;
    private String url;
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getTime() {
        return time;
    }
    public void setTime(String time) {
        this.time = time;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    @Override
    public String toString() {
        return "User [title=" + title + ", time=" + time + ", url=" + url + "]";
    }
}

3.编写HtmlParse.java

  • 流程
    通过2的分析,可以知道解析的流程为:

获取class为"nsy-news"的标签 --> 获取该标签下所有的<li></li>标签 --> 通过循环,获取<li></li>中的内容。

  • 注意的点
    获取<a></a>标签里面的href内容,使用的是element.attr("href")方法。
  • 代码
public class HtmlParser {

    public static List<User> parseData(String html) {
        if (html == null || html.length() < 1) {
            return null;
        }
        List<User> infoList = new ArrayList<User>();
        Document document = Jsoup.parse(html, "UTF-8");
        Elements elements = document.getElementsByClass("nsy-news");

        Elements liElements = elements.get(0).getElementsByTag("li");

        for (Element element : liElements) {

            Elements books = element.getElementsByTag("a");
            Elements times = element.getElementsByTag("p");

            User user = new User();
            user.setUrl(books.get(0).attr("href"));
            user.setTitle(books.get(0).text());
            user.setTime(times.get(0).text());

            infoList.add(user);

        }
        return infoList;
    }
}

4.定义规则

  • 内容
    在获取到了公告内容后,我们要实现在什么情况下,判定已经最新的招聘公告已发出。通过对公告内容的观察,决定定义两条规则:

1、String s = "2017年绍兴市上虞区中小学幼儿园教师公开招聘";
2、String s2 = "2017-03-10";

  • 代码
    此时,我们的代码成了这样。
public static void main(String[] args) throws InterruptedException, IOException {

        String url = "http://www.syjy.cn/moreInfo.aspx?layoutTemplateId=135&bigClassPkId=435";
        String s = "2017年绍兴市上虞区中小学幼儿园教师公开招聘";
        String s2 = "2017-03-10";
        while (true) {

            String result = HttpUtil.sendPost(url, null);
            List<User> users = HtmlParser.parseData(result);
            System.out.println("公告数:"+users.size());
            boolean flag = false;
            String newsUrl = null;
            for (int i = 0; i < users.size(); i++) {
                User user = users.get(i);
                if (user.getTime().equals(s2)){
                    System.out.println("日期:"+s2+" 的新闻:"+user.toString());
                    if(user.getTitle().contains(s)) {
                        newsUrl = user.getUrl();
                        flag = true;
                        continue;
                    }
                }
            }
            SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
            long msl = System.currentTimeMillis();
            String time=simpleDateFormat.format(msl); 
            if (flag) {
                System.out.println("时间: "+time+" ,状态: "+"已公布。");    
                // 在此处发送邮件
                break;
            }else {
                System.out.println("时间: "+time+" ,状态: "+"未公布。");    
            }
            Thread.sleep(60000);
        }
    }

5.实现发送邮件

  • 准备
    下载mail-1.4.1.jar
  • 代码
    代码里面xxxxx的内容需要根据自己的实际情况填写。示例为139邮箱的smtp地址和端口。QQ邮箱的smtp地址和端口为:smtp.qq.com:465务必根据实际情况填写。
public class SendMail {

    public static void send(String content){

        String smtpHost = "smtp.139.com";
        String from = "xxxxxxxxxxx@139.com";
        String to = "xxxxxxxxx@qq.com"; 
        String port = "25";
        Properties props =  System.getProperties();

        props.put("mail.smtp.host", smtpHost);
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.port", port); 
        props.setProperty("mail.transport.protocol", "smtp");

        MyAuthenticator myauth = new MyAuthenticator("xxxxxxxxxxx@139.com", "xxxxxx");
        Session session = Session.getDefaultInstance(props, myauth);
        session.setDebug(true);

        MimeMessage message = new MimeMessage(session);

        // Set the from address
        try {
            message.setFrom(new InternetAddress(from));
            // Set the to address
            message.addRecipient(RecipientType.TO, new InternetAddress(to));

            // Set the subject
            message.setSubject("2017年绍兴市上虞区中小学幼儿园教师招聘");

            // Set the content
            message.setText("公告已发出。链接地址:"+content);

            message.saveChanges();

            Transport.send(message);

        } catch (AddressException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (MessagingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static class MyAuthenticator extends Authenticator{
         private String strUser;
            private String strPwd;
            public MyAuthenticator(String user, String password) {
              this.strUser = user;
              this.strPwd = password;
            }

            protected PasswordAuthentication getPasswordAuthentication() {
              return new PasswordAuthentication(strUser, strPwd);
            }
    }
}

5.实现功能

  • 代码
    写到这里,我们把之前的内容都加上,实现了完整的代码。
public static void main(String[] args) throws InterruptedException, IOException {

        String url = "http://www.syjy.cn/moreInfo.aspx?layoutTemplateId=135&bigClassPkId=435";
        String s = "2017年绍兴市上虞区中小学幼儿园教师公开招聘";
        String s2 = "2017-03-10";
        while (true) {

            String result = HttpUtil.sendPost(url, null);
            List<User> users = HtmlParser.parseData(result);
            System.out.println("公告数:"+users.size());
            boolean flag = false;
            String newsUrl = null;
            for (int i = 0; i < users.size(); i++) {
                User user = users.get(i);
                if (user.getTime().equals(s2)){
                    System.out.println("日期:"+s2+" 的新闻:"+user.toString());
                    if(user.getTitle().contains(s)) {
                        newsUrl = user.getUrl();
                        flag = true;
                        continue;
                    }
                }
            }
            SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
            long msl = System.currentTimeMillis();
            String time=simpleDateFormat.format(msl); 
            if (flag) {
                System.out.println("时间: "+time+" ,状态: "+"已公布。");    

                CommandUtil.sendMail();
                SendMail.send(newsUrl);
                break;
            }else {
                System.out.println("时间: "+time+" ,状态: "+"未公布。");    
            }

            Thread.sleep(60000);
        }
    }
  • 日志
    一开始的日志都是这样的

公告数:27
日期:2017-03-10 的新闻:User [title=关于做好2017年度无偿献血工作的通知, time=2017-03-10, url=http://www.syjy.cn/newsInfo.aspx?pkId=18098]
时间: 2017-03-10 16:11:56 ,状态: 未公布。
公告数:27
日期:2017-03-10 的新闻:User [title=关于做好2017年度无偿献血工作的通知, time=2017-03-10, url=http://www.syjy.cn/newsInfo.aspx?pkId=18098]
时间: 2017-03-10 16:12:57 ,状态: 未公布。
公告数:27
日期:2017-03-10 的新闻:User [title=关于做好2017年度无偿献血工作的通知, time=2017-03-10, url=http://www.syjy.cn/newsInfo.aspx?pkId=18098]
时间: 2017-03-10 16:13:57 ,状态: 未公布。
公告数:27
日期:2017-03-10 的新闻:User [title=关于做好2017年度无偿献血工作的通知, time=2017-03-10, url=http://www.syjy.cn/newsInfo.aspx?pkId=18098]
时间: 2017-03-10 16:14:58 ,状态: 未公布。
公告数:27
日期:2017-03-10 的新闻:User [title=关于做好2017年度无偿献血工作的通知, time=2017-03-10, url=http://www.syjy.cn/newsInfo.aspx?pkId=18098]
时间: 2017-03-10 16:15:58 ,状态: 未公布。

  • 漫长的等待时间
    直到某一刻。

日期:2017-03-10 的新闻:User [title=关于做好2017年度无偿献血工作的通知, time=2017-03-10, url=http://www.syjy.cn/newsInfo.aspx?pkId=18098]
时间: 2017-03-10 16:46:59 ,状态: 未公布。
公告数:27
日期:2017-03-10 的新闻:User [title=关于2017年绍兴市上虞区中小学幼儿园教师公开招聘现场确认有关事项的通知, time=2017-03-10, url=http://www.syjy.cn/newsInfo.aspx?pkId=18100]
日期:2017-03-10 的新闻:User [title=关于做好2017年度无偿献血工作的通知, time=2017-03-10, url=http://www.syjy.cn/newsInfo.aspx?pkId=18098]
时间: 2017-03-10 16:48:00 ,状态: 已公布。
sendMail success

后记

当sendMail success出来时,说不出是什么滋味。半点成就感吧。或许这也是写代码的一种乐趣。生活的一个调味品。

最后的最后

最后的最后
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,755评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,305评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,138评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,791评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,794评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,631评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,362评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,264评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,724评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,040评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,742评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,364评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,944评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,060评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,247评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,979评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,659评论 18 139
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,424评论 0 17
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,639评论 18 399
  • # 一度蜜v3.0协议 --- # 交互协议 [TOC] ## 协议说明 ### 请求参数 下表列出了v3.0版协...
    c5e350bc5b40阅读 648评论 0 0
  • 郁郁葱葱的麦苗,在细雨的沁润下努力拔高自己..... 介绍伴侣不提它还真不行,优质奶源来自这里,每次都是到夜深人静...
    马艺马书芹阅读 209评论 0 1