9-某新闻网站爬取实战
来源:互联网 发布:暗黑血统终极版优化 编辑:程序博客网 时间:2024/06/05 15:40
某新闻网站爬取实战
1、需求说明
- 网址:https://www.huxiu.com/
- 爬取网站的文章,并保存到数据库
2、需求分析
- 爬取首页的数据
- 爬取分页的数据
- 创建数据库,保存数据
3、创建项目
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency> <!-- jsoup HTML parser library @ https://jsoup.org/ --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.2.6.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.41</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.31</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.1</version> </dependency>
4、代码开发(核心)
package huxiuSpider;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.util.ArrayList;import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import org.apache.http.client.ClientProtocolException;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.methods.HttpPost;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.message.BasicNameValuePair;import org.apache.http.util.EntityUtils;import org.jsoup.Jsoup;import org.jsoup.nodes.Document;import org.jsoup.nodes.Element;import org.jsoup.select.Elements;import com.alibaba.fastjson.JSON;/** * 开发虎嗅爬虫遇到的第一个问题 服务器返回500,简单的反爬虫技术。 * * @author maoxiangyi * */public class HuxiuSpider { private static ArticleDao articleDao = new ArticleDao(); private static ArrayBlockingQueue<String> urls = new ArrayBlockingQueue<String>(500); private static ExecutorService pool = Executors.newFixedThreadPool(10); public static void main(String[] args) throws Exception { // 初始化六个线程用来解析每个新闻详情页 for (int i = 0; i < 10; i++) { pool.submit(new Runnable() { public void run() { while (true) { try { String url = urls.take(); // 访问单个页面 得到html String html = getHtmlByGet(url, getHeaders()); if (html != null) { // 解析html文档,得到artice对象 Article article = parseArticle(html); // 保存数据库 save2db(article); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); } // 1、通过httpclient发起get请求 HashMap<String, String> headers = getHeaders(); String html = getHtmlByGet("https://www.huxiu.com/", headers); // 2、解析首页 parseIndex(headers, html); // 3、获取分页信息 下一页 String last_dateline = getDateLineByIndexHtml(html); paging(headers, last_dateline); } private static void paging(HashMap<String, String> headers, String last_dateline) throws UnsupportedEncodingException, IOException, ClientProtocolException, Exception { // 第二页 HttpPost httpPost = getPost(headers, last_dateline, 2); String html = getHtmlByPost(httpPost); // 解析json串 HuxiuResponse res = JSON.parseObject(html, HuxiuResponse.class); Document doc = Jsoup.parse(res.getData()); // url article Elements alist = doc.select("a[class=transition]"); for (Element element : alist) { urls.put("http://www.huxiu.com" + element.attr("href")); // html = getHtmlByGet("http://www.huxiu.com" + // element.attr("href"), headers); // Article article = parseArticle(html); // save2db(article); } // 做第三页 4 5 6 // 最终可以多少页 总共分页页码,获得lastdateline for (int page = 3; page <= res.getTotal_page(); page++) { HttpPost hp = getPost(headers, res.getLast_dateline(), page); html = getHtmlByPost(hp); res = JSON.parseObject(html, HuxiuResponse.class); doc = Jsoup.parse(res.getData()); // url article alist = doc.select("a[class=transition]"); for (Element element : alist) { urls.put("http://www.huxiu.com" + element.attr("href")); // html = getHtmlByGet("http://www.huxiu.com" + // element.attr("href"), headers); // Article article = parseArticle(html); // save2db(article); } System.out.println("-----------------------------分页完成------------------------"); } } private static String getHtmlByPost(HttpPost httpPost) throws IOException, ClientProtocolException { CloseableHttpClient pagingHttpClient = HttpClients.createDefault(); // 发起请求 CloseableHttpResponse paginHtml = pagingHttpClient.execute(httpPost); return EntityUtils.toString(paginHtml.getEntity()); } private static HttpPost getPost(HashMap<String, String> headers, String last_dateline, int page) throws UnsupportedEncodingException { String api = "https://www.huxiu.com/v2_action/article_list"; HttpPost httpPost = new HttpPost(api); // 提交一些参数 ArrayList<BasicNameValuePair> paramList = new ArrayList<BasicNameValuePair>(); paramList.add(new BasicNameValuePair("huxiu_hash_code", "a3bec0c023f9f2481ed8eeddf9c15225")); paramList.add(new BasicNameValuePair("page", page + "")); paramList.add(new BasicNameValuePair("last_dateline", last_dateline)); httpPost.setEntity(new UrlEncodedFormEntity(paramList)); // 提交请求头 for (Map.Entry<String, String> entry : headers.entrySet()) { httpPost.addHeader(entry.getKey(), entry.getValue()); } return httpPost; } private static void parseIndex(HashMap<String, String> headers, String html) { if (html != null) { getArticleListByIndex(html); // // 2.1、获取首页中每个url对应的详情页 // for (String url : articleUrls) { // /** // * 思考一个性能问题:for循环是依次迭代的,假设articleUrls.size=200, // * 抓取完所有的页面会需要很多时间,如何提高住区的速度? // * // * Java基础 多线程 一个进程中有个线程执行,可以提高处理速度。 // */ // new ProcessPageInfo(url, headers).start(); // } } } private static String getDateLineByIndexHtml(String html) { Document doc = Jsoup.parse(html); Elements eles = doc.select("div[data-last_dateline]"); return eles.get(0).attr("data-last_dateline"); } /** * 将新闻保存到数据库中 数据库的连接(mysql) 账户密码 连接url 连接(datasource) mybatis jdbctemplate * * @param article */ public static void save2db(Article article) { // 不能让数据库连接池 每次都创建 articleDao.save(article); } public static Article parseArticle(String html) { Article article = new Article(); Document doc = Jsoup.parse(html); // 获取标题 Elements titles = doc.select("h1.t-h1"); article.setTitle(titles.get(0).ownText()); // 获取作者信息 Elements authors = doc.select("span.author-name"); article.setAuthor(authors.get(0).text()); // 发布时间 Elements times = doc.select("span[class=article-time pull-left]"); article.setCreateTime(times.size() == 0 ? new Date().toString() : times.get(0).ownText()); // 收藏 Elements scs = doc.select("span[class=article-share pull-left]"); article.setSc(scs.size() == 0 ? "0" : scs.get(0).ownText().substring(2)); // 评论 Elements pls = doc.select("span[class=article-pl pull-left]"); article.setPl(pls.size() == 0 ? "0" : pls.get(0).ownText().substring(2)); // 获取赞 Elements zans = doc.select("span.num"); article.setZan(zans.get(0).ownText()); // 获取新闻的内容 Elements contents = doc.select("div.article-content-wrap"); article.setContent(contents.text()); System.out.println(article); return article; } private static void getArticleListByIndex(String html) { Document doc = Jsoup.parse(html); // 第一步 获取文章列表区域 Elements articleContent = doc.select("div.mod-info-flow"); Elements aTags = articleContent.select("a[class=transition]"); for (Element element : aTags) { String href = element.attr("href"); if (href.contains("article")) { // 第二步 获取每个新闻详情页的url try { urls.put("https://www.huxiu.com" + href); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } private static HashMap<String, String> getHeaders() { HashMap<String, String> headers = new HashMap<String, String>(); headers.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1;Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/59.0.3071.115 Safari/537.36"); return headers; } /** * 通过httpGet的方式获取网页的内容 * * @param url * 网络自愿抵制 * @param headers * 请求头 * @return 默认返回null,有也会返回null。200才返回数据。 */ public static String getHtmlByGet(String url, Map<String, String> headers) { String html = null; try { CloseableHttpClient hc = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); for (Map.Entry<String, String> entry : headers.entrySet()) { httpGet.addHeader(entry.getKey(), entry.getValue()); } CloseableHttpResponse response = hc.execute(httpGet); int code = response.getStatusLine().getStatusCode(); if (200 == code) { html = EntityUtils.toString(response.getEntity()); } else { System.out.println(code); System.out.println("请求url失败……" + url); } } catch (Exception e) { System.out.println(url); System.out.println("访问网络资源失败!" + e); } return html; }}
5、线程与线程池
线程是什么?
- 线程是运行在进程中的一个独立实体,是CPU调度和分派的基本单位。
- 线程基本不拥有系统资源,可以与同属一个进程的其它线程共享进程所用多的全部资源。
多线程是什么?
- 一个进程中多个线程的情况,我们叫做多线程。
- 多个线程会共享进程所拥有的全部资源。
减少创建线程的开销,有哪些开销?
- 关于时间,创建线程使用是直接向系统申请资源的,对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU 的缓存被 清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。
- 关于资源,Java线程的线程栈所占用的内存是在Java堆外的,所以是不受java程序控制的,只受系统资源限制,默认一个线程的线程栈大小是1M(当让这个可以通过设置-Xss属性设置,但是要注意栈溢出问题),但是,如果每个用户请求都新建线程的话,1024个用户光线程就占用了1个G的内存,如果系统比较大的话,一下子系统资源就不够用了,最后程序就崩溃了。
多线程的作用
通过多个线程并发执行,从而提高任务处理的速度。
创建几种方式
继承Thread类,并复写run方法,创建该类对象,调用start方法开启线程。
实现Runnable接口,复写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。
- 通过Callable和Future创建线程(略)
代码学习:
public static void main(String[] args) throws ExecutionException { //Callable的返回值就要使用Future对象,Callable负责计算结果,Future负责拿到结果 //1、实现Callable接口 Callable<Integer> callable = new Callable<Integer>() { public Integer call() throws Exception { int i=999; //do something // eg request http server and process return i; } }; //2、使用FutureTask启动线程 FutureTask<Integer> future = new FutureTask<Integer>(callable); new Thread(future).start(); //3、获取线程的结果 try { Thread.sleep(5000);// 可能做一些事情 System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }}
线程池 Executors类
- 创建固定数目线程的线程池
- 创建一个可缓存的线程池
- 定时及周期性的执行任务的线程池
- scheduleAtFixedRate 这个方法是不管你有没有执行完,反正我每隔几秒来执行一次,以相同的频率来执行
- scheduleWithFixedDelay 这个是等你方法执行完后,我再隔几秒来执行,也就是相对延迟后,以固定的频率去执行
代码学习
public static void testFixedThreadPool() { //创建固定的线程池,使用3个线程来并发执行提交的任务。底层是个无界队列 ExecutorService executorService = Executors.newFixedThreadPool(6); executorService.execute(new MyThread()); executorService.execute(new MyRunnable()); executorService.execute(new MyRunnable()); executorService.execute(new MyRunnable()); executorService.execute(new MyThread()); executorService.execute(new MyThread());}public static void testSingleThreadPool() { //创建单线程,在任务执行时,会依次执行任务。底层是个无界队列。 ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(new MyThread()); executorService.execute(new MyRunnable()); executorService.execute(new MyRunnable()); executorService.execute(new MyRunnable()); executorService.execute(new MyThread());}public static void testCacheThreadPool() { //创建非固定数量,可缓存的线程池。当提交的任务数量起起伏伏时,会自动创建或者减少执行线程的数量。 //当然,重用线程是线程池的基本特征。 ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(new MyThread()); executorService.execute(new MyRunnable()); executorService.execute(new MyRunnable()); executorService.execute(new MyRunnable()); executorService.execute(new MyThread());}public static void testScheduledThreadPool(){ //创建一个定时执行线程池 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(30); //1、配置任务的执行周期 //scheduleAtFixedRate 固定周期执行完毕 executorService.scheduleAtFixedRate(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS); //scheduleWithFixedDelay 上一次执行完毕之后下一次开始执行 executorService.scheduleWithFixedDelay(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);}public static void testSingleCacheThreadPool(){ //创建一个单个线程执行的定时器 ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); //scheduleAtFixedRate 固定周期执行完毕 executorService.scheduleAtFixedRate(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS); //scheduleWithFixedDelay 上一次执行完毕之后下一次开始执行 executorService.scheduleWithFixedDelay(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);}public static void testMyThreadPool(){ //自定义连接池稍微麻烦些,不过通过创建的ThreadPoolExecutor线程池对象,可以获取到当前线程池的尺寸、正在执行任务的线程数、工作队列等等。 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,100,10,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100)); threadPoolExecutor.execute(new MyThread()); threadPoolExecutor.execute(new MyRunnable()); threadPoolExecutor.execute(new MyRunnable());}
6、队列
队列基础
基本概念
- Queue用于模拟队列这种数据结构。
- 队列通常是指“先进先出(FIFO)”的容器。
- 队列的头部保存在队列中存放时间最长的元素,尾部保存存放时间最短的元素。
- 新元素插入到队列的尾部,取出元素会返回队列头部的元素。
- 通常,队列不允许随机访问队列中的元素。
- 在java5中新增加了java.util.Queue接口,用以支持队列的常见操作。该接口扩展了java.util.Collection接口。Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。 如果要使用前端而不移出该元素,使用element()或者peek()方法。
Queue的常用方法
- 插入数据
- void add(Object e): 将指定元素插入到队列的尾部。
- boolean offer(Object e): 将指定的元素插入此队列的尾部。当使用容量有限的队列时,此方法通常比add(Object e)有效。
- 移除
- Object remove(): 获取队列头部的元素,并删除该元素。
- Object poll(): 返回队列头部的元素,并删除该元素。如果队列为空,则返回null。
- 检查
- object element(): 获取队列头部的元素,但是不删除该元素。
- Object peek(): 返回队列头部的元素,但是不删除该元素。如果队列为空,则返回null。
注意一点就好,Queue通常不允许插入Null,尽管某些实现(比如LinkedList)是允许的,但是也不建议。
双端队列
Queue还有一个Deque接口,Deque代表一个“双端队列”,双端队列可以同时从两端删除或添加元素,因此Deque可以当作栈来使用。java为Deque提供了ArrayDeque实现类和LinkedList实现类。
public static void main(String[] args) { ArrayDeque queue = new ArrayDeque(); queue.offer("春"); queue.offer("夏"); queue.offer("秋"); System.out.println(queue); System.out.println(queue.peek()); System.out.println(queue); System.out.println(queue.poll()); System.out.println(queue);}
在多线程下queue有什么问题?
最主要的问题,重复消费,造成数据混乱
阻塞队列
BlockingQueue,如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒.同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作
BlockingQueue阻塞队列
- ArrayBlockingQueue
- LinkedBlockingQueue
常见操作
- add():把anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则报异常
- offer():表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.
- put():把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
- poll():取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null
- take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止
public static void main(String[] args) { final BlockingQueue queue = new ArrayBlockingQueue(3); //创建三个线程 生产数据 put for (int i = 0; i < 2; i++) { new Thread() { public void run() { while (true) { try { Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + "--------------准备放数据!"); queue.put(1); System.out.println(Thread.currentThread().getName() + "--------------已经放了数据," + "队列目前有" + queue.size() + "个数据"); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } //创建一个线程消费数据 take 数据 new Thread() { public void run() { while (true) { try { //将此处的睡眠时间分别改为100和1000,观察运行结果 Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "准备取数据!"); queue.take(); System.out.println(Thread.currentThread().getName() + "已经取走数据," + "队列目前有" + queue.size() + "个数据"); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start();}
阅读全文
1 0
- 9-某新闻网站爬取实战
- Python爬虫爬取网站新闻
- python3爬取新闻网站的所有新闻-新手起步
- 爬取网易的新闻网站全函数
- 爬虫第四课(RegEx爬取新闻网站)
- 爬虫实战--爬取juubao购物券网站
- java新闻爬取
- 爬取新闻时间
- python3爬虫 爬取图片,爬取新闻网站文章并保存到数据库
- python爬虫实战(2)——爬取腾讯新闻
- Jsoup爬取网易新闻
- python_爬取新浪新闻
- Python爬取新浪新闻
- 爬取一个普通翻页的新闻网站代码样例
- scrapy实战-爬取
- WAP网站源代码--WAP新闻(文章)系统调试实战zz
- 某网站滚动新闻源码
- 腾讯新闻评论数据爬取
- 第二章 面向对象设计原则实训
- java入门知识
- 27. Remove Element
- hibernate的对象状态
- Flex实现网页模态框
- 9-某新闻网站爬取实战
- Ubantu16.04配置Matconvnet
- MySQL从头至尾汇总(6.函数篇-转载,修正)
- 07-图6 旅游规划(25 分)
- 文章标题
- 【读书笔记】沉默的大多数
- PAT考试乙级1048(C语言实现)重点题目(思路、用到了memset)
- 【 转载】HTML 中标签 <meta name="robots" content=""> 的含义
- 推荐算法--基于物品的协同过滤算法