java实现简单的网络爬虫+邮件推送

       专业做法:URL放在队列里面,如果被访问过就设置个标记,直到遍历完所有的URL。
Alt


一、搭建maven环境


I eclipse搭建maven环境(更快捷的管理jar包)

1、网上下载maven

       下载地址:http://maven.apache.org/download.cgi

2、配置环境变量

       ① 新建系统变量如下(本地的maven路径):

Alt

       ② path中加入如下路径(win10):
Alt


       ③ 测试安装是否完成:

Alt

3、配置本地仓库

       ① Window->Preferences->Maven->Installations选中add,加入自己本地的maven路径。

Alt

       ② 第一步之后会默认启用maven/conf路径下的settings.xml文件,在这个文件中配置本地仓库,相应的改成自己的路径。repository用来存放jar包(顺便还可以配置个阿里的镜像)。

Alt

4、配置jar包

       直接在pom.xml文件的dependencies节点中添加依赖即可。

       查找jar包的网址:https://mvnrepository.com/tags/maven


       eclipse快速输出:syso + alt + /
       eclipse快速设置get set方法:右击Source->Generate Getters() and Setters()



II IDEA搭建maven环境

1、添加本地maven仓库

       配置好后会自动下载需要的jar包。

       如果最后没有出现Build Success,可能是网络不稳定,建议重新再来一遍,或者删除以lastupdate为后缀名的文件,再次更新maven project。依赖项的添加和eclipse一样。

Alt


2、reimport maven project(PS:IDEA好像只能适配maven3.5)

       设置好后还是发现pom.xml文件报错。View->Tool Windows->Maven,点击下图的Reimport All Maven Projects。

Alt



3、添加run

       Main class为你要运行的类,并设置好自己的jre。

Alt

       IDEA多行选中:Shift + Alt +鼠标左键
       IDEA移动代码块:选中后Shift + Alt +上下箭头
       IDEA全部查找:两下Shift
       IDEA快速输出:sout
       IDEA快速补全变量:写好方法后Alt + Enter




二、java爬虫部分

1、静态页面爬取

       爬取豆瓣电影排行榜的一些数据举例子。

       静态页面的爬取可以用HttpClient的jar包下载页面,用HtmlCleaner的jar包解析页面。

       实体类Page查看


① 页面下载

       Page是自己建的实体类。先使用HttpClient 创建客户端,根据URL创建请求,客户端提交请求得到响应,(虚拟服务端)将响应的内容返回,其中content中包含了整个页面的html代码块,下一步需要对代码进行解析。

	public class PageDownloadUtil {
	
	    public static Page getPageContent(String url) {
	
	        //创建客户端
	        HttpClient client = HttpClients.createDefault();
	
	        //创建get方法并添加请求头
	        HttpGet request = new HttpGet(url);
	        //request.setHeader("user-agent", "");   //可以设置请求头
	
	        HttpResponse response = null;
	        try {
	            response = client.execute(request);
	        } catch (ClientProtocolException e) {
	            e.printStackTrace();
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	
	        //将得到的实体转换成字符串类型
	        HttpEntity entity = response.getEntity();
	        String content = null;
	        try {
	            content = EntityUtils.toString(entity, "UTF-8");
	        } catch (ParseException e) {
	            e.printStackTrace();
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	        Page page = new Page();
	        page.setContent(content);
	        page.setUrl(url);
	        page.setDownnumber("0");
	        page.setUpnumber("0");
	        return page;
	    }
	}

② 页面解析

       使用Htmlcleaner的clean方法清洗上一步的html代码,再根据XPath定位到对应的标签(用Chrome浏览器别用Firefox),最后用正则表达式匹配出想要的数据,存放到容器里面。

	public class DouBanProcessService {
	
	    private static String allnumberRegex = "\\d+";
	    private static String allchineseRegex = "[\\u4E00-\\u9FA5]+";
	
	    public static Page process(Page page) {
	
	        String content = page.getContent();
	
	        // 获取整个html的node
	        HtmlCleaner htmlCleaner = new HtmlCleaner();
	        TagNode rootNode = htmlCleaner.clean(content);
	        List<String> list=new ArrayList<String>();
	        int i=0;
	        try {
	            Object[] numberXPath = rootNode
	                    .evaluateXPath("//*[@id=\"9593388\"]/div/div[3]");
	            if (numberXPath.length > 0) {
	                TagNode node = (TagNode) numberXPath[0];
	                Pattern numberPattern = Pattern.compile(allnumberRegex,
	                        Pattern.DOTALL);
	                Matcher matcher1 = numberPattern.matcher(node.getText()
	                        .toString());
	                while (matcher1.find()) {
	                    list.add(matcher1.group(0));
	                    System.out.println(matcher1.group(0));
	                }
	            }
	            page.setUpnumber(list.get(i++));
	            page.setDownnumber(list.get(i++));
	            page.setCommentumber(list.get(i++));
	
	            Object[] nameXPath = rootNode
	                    .evaluateXPath("//*[@id=\"9593388\"]/header/a[2]");
	            if (nameXPath.length > 0) {
	                TagNode node = (TagNode) nameXPath[0];
	                Pattern namePattern = Pattern.compile(allchineseRegex,
	                        Pattern.DOTALL);
	                Matcher matcher2 = namePattern.matcher(node.getText()
	                        .toString());
	                while (matcher2.find()) {
	                    page.setName(matcher2.group(0));
	                }
	            }
	        } catch (XPatherException e) {
	            e.printStackTrace();
	        }
	        return page;
	    }
	
	    public static void main(String[] args) {
	
	        String url = "https://movie.douban.com/review/best/";
	        // 下载页面
	        Page page= PageDownloadUtil.getPageContent(url);
	        Page after_page=DouBanProcessService.process(page);
	        System.out.println("电影名字:"+page.getName()+"  点赞人数:"+page.getUpnumber()+
	                "  差评人数:"+page.getDownnumber()+"  评论人数:"+page.getCommentumber());
	    }
	}

③ 结果

       结果显示简单的静态页面被爬取下来了。

Alt




2、动态页面爬取

       爬取爱奇艺海贼王连载的每集的标题举例子。

       动态页面下载就需要用到HtmlUnit的jar包,用来模拟点击事件。针对Ajax异步请求,通过观察URL的规律,直接暴力抓取json数据包的URL。页面解析可以用Jsoup的jar包,操作比HtmlCleaner更简单。

       实体类ResultMap查看


① 页面下载

       第一次请求(从左图到右图):
Alt Alt


       jsoup的jar包解析Dom树的结点,可以很方便的select出想操作的节点,newURL返回的是上图第二个页面的链接。

	public class SimulatedLoginUtil {
	
		public static String getnewURL(String url) {
	
			// jsoup模拟海贼王超链接的点击事件,返回新的URL
			String newURL = null;
			// System.getProperties().setProperty("proxySet", "true"); 代理协议
			// System.getProperties().setProperty("https.proxyHost",
			// "192.168.130.15"); 代理IP
			// System.getProperties().setProperty("https.proxyPort", "8848"); 代理端口
			Connection con = Jsoup.connect(url);// 获取连接
	
			con.header(
					"User-Agent",
					"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0");// 配置模拟浏览器
			con.header("Accept-Language", "zh-CN");
			Response rs = null;
			try {
				rs = con.execute();
				Document dom = Jsoup.parse(rs.body());// 转换为Dom树
				// 直接再Dom树里面选择(根据id或者class)
				List<Element> et = dom.select(".search-box-input");
				// 在搜索框中设值,这个中文字还要转成UTF-8格式,在字符串处理模块再转
				Element inputtext = et.get(0).attr("value", "海贼王");
				String des_keyword = inputtext.attr("value");
				newURL = StringProcessUtil.produceFornewurl(url, des_keyword); //改变URL并转码
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			...续第二次请求
		}
	}

       第二次请求(从左图到右图):
Alt Alt

       先在新的URL(已经转成UTF-8格式)里面查找跳转到下个页面的超链接,HtmlUnit模拟页面的的点击,finallyURL返回的也是上图第二个页面的链接。

	public class SimulatedLoginUtil {
	
		public static String getnewURL(String url) {
			
			...续第一次请求
			// htmlunit模拟网页点击,选中要查看的海贼王非剧场版动漫
			WebClient client = new WebClient(BrowserVersion.getDefault());
			client.getOptions().setCssEnabled(false);
			client.getOptions().setJavaScriptEnabled(false);
			client.getOptions().setTimeout(10000);
			client.getOptions().setThrowExceptionOnScriptError(false);
			client.getOptions().setThrowExceptionOnFailingStatusCode(false);
			client.getOptions().setRedirectEnabled(true);
			// client.getCookieManager().setCookiesEnabled(true);
			// client.getOptions().setUseInsecureSSL(true);
			client.setAjaxController(new NicelyResynchronizingAjaxController());
	
			String finallyURL = null;
			HtmlPage page = null, next_page = null;
			int k = 0, flag = 0;
			try {
				page = client.getPage(newURL);
				System.out.println(newURL);
				// Dom树中遍历
				DomNodeList<DomElement> domlist = page.getElementsByTagName("a");
				for (int i = 0; i < domlist.getLength(); i++) {
					DomElement domElement = domlist.get(i);
					if (domElement.asText().equals("航海王")) {
						k = i;
						flag = 1;
						break;
					}
				}
				if (flag == 0)
					System.out.println("没找到超链接a");
				HtmlElement e = (HtmlElement) domlist.get(k);
				client.waitForBackgroundJavaScript(3000); // 等待js执行完毕
				// 模拟点击事件
				next_page = e.click();
				client.waitForBackgroundJavaScript(3000); // 等待js执行完毕
				finallyURL = next_page.getUrl().toString();
	
			} catch (FailingHttpStatusCodeException e3) {
				e3.printStackTrace();
			} catch (MalformedURLException e3) {
				e3.printStackTrace();
			} catch (IOException e3) {
				e3.printStackTrace();
			}
			return finallyURL;
		}
	}



② 字符串处理

       produceFornewurl(String url, String keyword)函数不仅要转成UTF-8编码,还要修改网址,其中跳转后的网址将www换成了so。

	public class StringProcessUtil {
	
		...继清洗数据
		
		public static String produceFornewurl(String url, String keyword) {
			String newURL = null;
			String encodekeyword = null;
			int index_point = url.indexOf(".");
			int index_www = url.indexOf("w");
			newURL = url.substring(0, index_www) + "so"
					+ url.substring(index_point);
	
			try {
				// URL中的中文要转成UTF-8
				encodekeyword = URLEncoder.encode(keyword, "UTF-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			newURL = newURL + "so/q_" + encodekeyword;
			return newURL;
		}
	}



③ 页面解析(包括第二次请求的转编码格式)

       观察法,查看URL的变化规律,其实就是page后面变换值。打开缓冲区,用StringBuilder类接收json中的数据,再HtmlCleaner+Regex抓取最终页面中的数据。340集标题名字残缺真是很骚气,找了半天才发现是网页的问题。。。

	public class DynamicDownload {
	
		public static Map<String, String> dynamicdownloadForonePage(String url,
				int pageindex) {
	
			String content = null;
			String oldurl = "https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid=202861101&size=50&page=";
			String newurl = oldurl + pageindex;
			StringBuilder json = new StringBuilder();
			URLConnection uc = null;
			BufferedReader in = null;
			String str = null;
			URL urlobject = null;
	
			// 根据URL直接抓取json包
			try {
				urlobject = new URL(newurl);
				uc = urlobject.openConnection();
				//打开缓冲区
				in = new BufferedReader(new InputStreamReader(
						urlobject.openStream(), "UTF-8"));
				while ((str = in.readLine()) != null)
					json.append(str);
				if (in != null)
					in.close();
			} catch (MalformedURLException e1) {
				e1.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
	
			// 清洗需要的内容,并放到map里面
			content = json.toString();
			HtmlCleaner cleaner = new HtmlCleaner();
			TagNode rn = cleaner.clean(content);
			Map<String, String> rs = new LinkedHashMap<String, String>();
			String temp1, temp2;
			//regex匹配
			Pattern p_number = Pattern.compile("\"order\":\\d+");
			Pattern p_name = Pattern.compile("subtitle\":\"[^\"]+\",\"vid");
			Matcher m_number = p_number.matcher(rn.getText().toString());
			Matcher m_name = p_name.matcher(rn.getText().toString());
			while (m_number.find() && m_name.find()) {
				temp1 = m_number.group(0);
				temp2 = m_name.group(0);
				rs.put(temp1, temp2);
			}
	
			// 修复爱奇艺页面中的340集名字残缺
			List<String> list = new ArrayList<>();
			int flag = 0, i = 0;
			if (pageindex == 7) {
				for (String key : rs.keySet()) {
					if ("\"order\":340".equals(key))
						flag = 1;
					if (flag == 1) {
						String value = rs.get(key);
						list.add(value);
					}
				}
				flag = 0;
				for (String key : rs.keySet()) {
					if ("\"order\":340".equals(key)) {
						flag = 1;
						rs.put(key, "subtitle\":\"被称为天才的男人! 霍古巴可现身\",\"vid");
						continue;
					}
					if (flag == 1) {
						rs.put(key, list.get(i++));
					}
				}
				// rs.put("\"order\":350", list.get(i++));
			}
			return rs;
		}
	}



④ 数据清洗

       前面哈希表里面存放的是下图,需要清洗数据,重新存放到哈希表里面。

       cutForonepice(Map<String, String> m)函数是清洗map里面的数据,保存成集号和标题的形式。

Alt

	public class StringProcessUtil {
	
		public static Map<String, String> cutForonepice(Map<String, String> m) {
			Map<String, String> rs = new LinkedHashMap<String, String>();
			String right = "340";
			int index1, index2, index3;
			String newkey, newvalue;
			for (String key : m.keySet()) {
				index1 = key.indexOf(":", 0);
				newkey = key.substring(index1 + 1, key.length());
				String value = m.get(key);
				index2 = value.indexOf(":", 0);
				index3 = value.indexOf(",", 0);
				newvalue = value.substring(index2 + 2, index3 - 1);
				rs.put(newkey, newvalue);
			}
			return rs;
		}
		...续字符串处理
	}



⑤ 入口类
/*
 * 海贼王onepice执行入口
 */

	public class StartCrawl {
	
	    public static void main(String[] args) {
	        ResultMap object = new ResultMap();
	        Map<String, String> des_map = new LinkedHashMap<String, String>();
	        String url = "https://www.iqiyi.com";
	        String des_url = SimulatedLoginUtil.getnewURL(url);
	        for (int i = 1; i <= 19; i++) {
	            Map<String, String> rs = DynamicDownload.dynamicdownloadForonePage(
	                    des_url, i);
	            des_map.putAll(rs);
	            System.out.println("第" + i + "页  已经抓取完成");
	        }
	        object.setTree(des_map);
	        object.setTree(StringProcessUtil.cutForonepice(object.getTree()));
	        System.out.println("全部抓取完毕");
	        for (String key : object.getTree().keySet()) {
	            String value = object.getTree().get(key);
	            System.out.println(key + "  " + value);
	        }
	    }
	}



⑥ 结果

       成功取到想要的数据,可以将它放到数据库或者存到本地的excel、txt等等格式。

Alt




三、邮件推送

1、设置开启服务(网易邮箱为例)

       开启相应权限。

Alt

Alt



2、代码

	public class SendEmailUtil {
	
	    public static void send() {
	        final String send_account = "cj1561435010@163.com";
	        final String send_password = ReadFileUtil.readTextFile("D:\\IDEAproject\\Spider\\pwd.txt").trim();    // 密码或者是你自己的设置的授权码
	
	        // SMTP服务器
	        String MEAIL_163_SMTP_HOST = "smtp.163.com";
	        String SMTP_163_PORT = "25";// 端口号,这个是163使用到的;
	        // 收件人
	        String receive_account = "cj1561435010@163.com";
	        Properties p = new Properties(); //创建连接对象
	        //Properties.
	        p.put("mail.transport.protocol", "smtp");  //设置邮件发送的协议
	        p.setProperty("mail.smtp.host", "smtp.163.com");   //设置发送邮件的服务器(这里用的163-SMTP服务器)
	        p.setProperty("mail.smtp.auth", "true");   //设置经过账号密码的授权
	        //设置服务器的端口号
	        p.setProperty("mail.smtp.port", "25");
	        p.setProperty("mail.smtp.socketFactory.port", "25");
	        p.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
	        Session session = Session.getInstance(p, new Authenticator() {
	            // 设置认证账户信息
	            @Override
	            protected PasswordAuthentication getPasswordAuthentication() {
	                return new PasswordAuthentication(send_account, send_password);
	            }
	        });
	
	        try {
	            MimeMessage message = new MimeMessage(session);   //创建邮件对象
	            message.setFrom(new InternetAddress(send_account)); //设置发件人
	            message.setRecipients(Message.RecipientType.TO, receive_account);
	            message.setRecipients(Message.RecipientType.CC, send_account); //重点:最好写这个抄送,不然大概率会被当成垃圾邮件!!
	            message.setFrom(new InternetAddress(send_account));
	            message.setSubject("one pice!");
	            message.setText("海贼王更新了!!!");
	            message.setSentDate(new Date());
	            message.saveChanges();
	            Transport.send(message);
	        } catch (MessagingException e) {
	            e.printStackTrace();
	        }
	    }
	}

	public class ReadFileUtil {
	    public static String readTextFile(String path) {
	        StringBuilder content = new StringBuilder("");
	        try {
	            File file = new File(path);
	            BufferedReader buffer = new BufferedReader(new FileReader(file));
	            String line = null;
	            while ((line = buffer.readLine()) != null) {
	                content.append(line);
	                content.append("\n");
	            }
	            buffer.close();
	        } catch (Exception e) {
	            System.out.println("读取内容出错");
	            e.printStackTrace();
	        }
	        return content.toString();
	    }
	
	    public static void main(String[] args) {
	        String x=ReadFileUtil.readTextFile("D:\\IDEAproject\\Spider\\pwd.txt");
	        System.out.println(x);
	    }
	}


3、结果

Alt

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值