懒惰的人总有方法不让自己去做简单、重复的事情。
缘由
今天是3月10号,是绍兴市上虞区教育体育局发布2017年绍兴市上虞区中小学幼儿园教师公开招聘的现场确认通知的时候。早上开始,女朋友就开始关注到底什么时候会发布这个通知。那传统的方式就是打开网站,查看通知通告到底有没有发送,但是作为一个程序员,感觉这事情有点简单、重复。那么作为一个懒人,就开始着手设计一套自己能跑起来的程序,来代替自己做事。
想要实现的功能
- 定时访问网页
- 获取网页内容解析
- 判断是否已发送公告,实现发送邮件
实现的效果图
着手实施
1.定时访问网页
-
定时
此处使用简单的while(true)
,作为死循环,然后通过Thread.sleep()
来实现定时效果。所以,一开始的代码是这样的:
public static void main(String[] args) {
while (true) {
System.out.println("啦啦啦");
Thread.sleep(1000);
}
}
-
访问网页
这里使用了一个简单的HttpUtil,来实现get和post两种访问方式。访问的地址是:http://www.syjy.cn/moreInfo.aspx?layoutTemplateId=135&bigClassPkId=435
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给封了。后来打开网页出现的是这个页面。
服务器错误
2.解析网页内容,获取重要线索
-
工具准备
解析网页,我用到的是jsoup-1.7.1.jar。 -
分析网页内容
获取网页内容后,找到了公告列表的html内容,如下图。
公告内容<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出来时,说不出是什么滋味。半点成就感吧。或许这也是写代码的一种乐趣。生活的一个调味品。