主要难点:

1.并发线程的控制 采用了JDK5的UTIL包里的concurrent子包

2.去重

3.序列化

运行方法:java -Xms128M -Xmx512M -jar JavaCrawler.jar http://foxhq.com/ C:/a.log 0 D:/pic D:/url.tmp D:/img.tmp

SimpleBloomFilter.java

package com.hengking.crawl; import java.io.Serializable; import java.util.BitSet; public class SimpleBloomFilter implements Serializable { /** * */ private static final long serialVersionUID = 1L; private final int DEFAULT_SIZE = 2 << 24; private final int[] seeds = new int[] { 7, 11, 13, 31, 37, 61, }; private BitSet bits = new BitSet(DEFAULT_SIZE); private SimpleHash[] func = new SimpleHash[seeds.length]; // public void main(String[] args) { // String value = "stone2083@yahoo.cn"; // SimpleBloomFilter filter = new SimpleBloomFilter(); // System.out.println(filter.contains(value)); // filter.add(value); // System.out.println(filter.contains(value)); // } public SimpleBloomFilter() { for (int i = 0; i < seeds.length; i++) { func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]); } } public void add(String value) { for (SimpleHash f : func) { bits.set(f.hash(value), true); } } public boolean contains(String value) { if (value == null) { return false; } boolean ret = true; for (SimpleHash f : func) { ret = ret && bits.get(f.hash(value)); } return ret; } public class SimpleHash implements Serializable { private int cap; private int seed; public SimpleHash(int cap, int seed) { this.cap = cap; this.seed = seed; } public int hash(String value) { int result = 0; int len = value.length(); for (int i = 0; i < len; i++) { result = seed * result + value.charAt(i); } return (cap - 1) & result; } } @Override public String toString() { // TODO Auto-generated method stub return super.toString(); } }

UtilSeriz.java

package com.hengking.crawl; import java.io.*; public class UtilSeriz { /** *将对象序列化到磁盘文件中 *@param *@throwsException */ public static void writeObject(Object o,String strPath) throws Exception{ File f=new File(strPath); if(f.exists()){ f.delete(); } FileOutputStream os=new FileOutputStream(f); //ObjectOutputStream 核心类 ObjectOutputStream oos=new ObjectOutputStream(os); oos.writeObject(o); oos.close(); os.close(); } /** *反序列化,将磁盘文件转化为对象 *@paramf *@return *@throwsException */ public static Object readObject(String strPath) throws Exception{ File f=new File(strPath); if(!f.exists()) { return null; } InputStream is=new FileInputStream(f); //ObjectOutputStream 核心类 ObjectInputStream ois=new ObjectInputStream(is); return ois.readObject(); } }

SearchCrawler.java

package com.hengking.crawl; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import com.hengking.crawl.po.PoCalSearch; import com.hengking.crawl.po.PoDownload; /*** * 说明:抓图工具 * @author 君望永远 * */ public class SearchCrawler implements Runnable{ /* disallowListCache缓存robot不允许搜索的URL。 Robot协议在Web站点的根目录下设置一个robots.txt文件, *规定站点上的哪些页面是限制搜索的。 搜索程序应该在搜索过程中跳过这些区域,下面是robots.txt的一个例子: # robots.txt for http://somehost.com/ User-agent: * Disallow: /cgi-bin/ Disallow: /registration # /Disallow robots on registration page Disallow: /login */ public static SimpleBloomFilter filterUrl; public static SimpleBloomFilter filterImg; private HashMap< String,ArrayList< String>> disallowListCache = new HashMap< String,ArrayList< String>>(); ArrayList< String> errorList= new ArrayList< String>();//错误信息 ArrayList< String> result=new ArrayList< String>(); //搜索到的结果 String startUrl;//开始搜索的起点 LinkedHashSet<String> toCrawlList = new LinkedHashSet<String>(); boolean caseSensitive=false;//是否区分大小写 boolean limitHost=false;//是否在限制的主机内搜索 private static String outdir; private static String seroutdir; private static String seroutdirimg; private boolean blnFlag=false; private static PoCalSearch ps=null; private static PoDownload pd=null; //300个图片分析线程 private static ExecutorService execImg; final Semaphore sempImg = new Semaphore(300); //30个网页分析线程 private static ExecutorService execPage; final Semaphore sempPage = new Semaphore(30); private ArrayList<ParsePage> arrPar=new ArrayList<ParsePage>(); //记录抓图结果 private static BufferedWriter bw = null; public SearchCrawler(String startUrl) { this.startUrl=startUrl; } public ArrayList< String> getResult(){ return result; } public void run(){//启动搜索线程 new Thread(new TimeWrite2File()).start(); blnFlag=true; crawl(startUrl,limitHost,caseSensitive); } //检测URL格式 private URL verifyUrl(String url) { // 只处理HTTP URLs. if (!url.toLowerCase().startsWith("http://")) return null; URL verifiedUrl = null; try { verifiedUrl = new URL(url); } catch (Exception e) { return null; } return verifiedUrl; } // 检测robot是否允许访问给出的URL. private boolean isRobotAllowed(URL urlToCheck) { String host = urlToCheck.getHost().toLowerCase();//获取给出RUL的主机 //System.out.println("主机="+host); // 获取主机不允许搜索的URL缓存 ArrayList< String> disallowList =disallowListCache.get(host); // 如果还没有缓存,下载并缓存。 if (disallowList == null) { disallowList = new ArrayList< String>(); try { URL robotsFileUrl =new URL("http://" + host + "/robots.txt"); BufferedReader reader =new BufferedReader(new InputStreamReader(robotsFileUrl.openStream())); // 读robot文件,创建不允许访问的路径列表。 String line; while ((line = reader.readLine()) != null) { if (line.indexOf("Disallow:") == 0) {//是否包含"Disallow:" String disallowPath =line.substring("Disallow:".length());//获取不允许访问路径 // 检查是否有注释。 int commentIndex = disallowPath.indexOf("#"); if (commentIndex != - 1) { disallowPath =disallowPath.substring(0, commentIndex);//去掉注释 } disallowPath = disallowPath.trim(); disallowList.add(disallowPath); } } // 缓存此主机不允许访问的路径。 disallowListCache.put(host, disallowList); } catch (Exception e) { return true; //web站点根目录下没有robots.txt文件,返回真 } } String file = urlToCheck.getFile(); //System.out.println("文件getFile()="+file); for (int i = 0; i < disallowList.size(); i++) { String disallow = disallowList.get(i); if (file.startsWith(disallow)) { return false; } } return true; } private String downloadPage(URL pageUrl) { try { // Open connection to URL for reading. BufferedReader reader = new BufferedReader(new InputStreamReader(pageUrl.openStream())); // Read page into buffer. String line; StringBuffer pageBuffer = new StringBuffer(); while ((line = reader.readLine()) != null) { pageBuffer.append(line); } return pageBuffer.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } // 从URL中去掉"www" private String removeWwwFromUrl(String url) { int index = url.indexOf("://www."); if (index != -1) { return url.substring(0, index + 3) + url.substring(index + 7); } return (url); } // 解析页面并找出链接 private ArrayList< String> retrieveLinks(URL pageUrl, String pageContents, boolean limitHost) { // 用正则表达式编译链接的匹配模式。 Pattern p =Pattern.compile("<a//s+href//s*=//s*/"?(.*?)[/"|>]",Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(pageContents); ArrayList< String> linkList = new ArrayList< String>(); while (m.find()) { String link = m.group(1).trim(); if (link.length() < 1) { continue; } // 跳过链到本页面内链接。 if (link.charAt(0) == '#') { continue; } if (link.indexOf("mailto:") != -1) { continue; } if (link.toLowerCase().indexOf("javascript") != -1) { continue; } if (link.indexOf("://") == -1){ if (link.charAt(0) == '/') {//处理绝对地 link = "http://" + pageUrl.getHost()+":"+pageUrl.getPort()+ link; } else { String file = pageUrl.getFile(); if (file.indexOf('/') == -1) {//处理相对地址 link = "http://" + pageUrl.getHost()+":"+pageUrl.getPort() + "/" + link; } else { String path =file.substring(0, file.lastIndexOf('/') + 1); link = "http://" + pageUrl.getHost() +":"+pageUrl.getPort()+ path + link; } } } int index = link.indexOf('#'); if (index != -1) { link = link.substring(0, index); } link = removeWwwFromUrl(link); URL verifiedLink = verifyUrl(link); if (verifiedLink == null) { continue; } /* 如果限定主机,排除那些不合条件的URL*/ if (limitHost && !pageUrl.getHost().toLowerCase().equals( verifiedLink.getHost().toLowerCase())) { continue; } // 跳过那些已经处理的链接. if(filterUrl.contains(link)) { logEvent("匹配了:"+link); continue; } else { filterUrl.add(link); } linkList.add(link); } return (linkList); } // 解析页面并找出链接 private ArrayList< String> retrieveImgLinks(URL pageUrl, String pageContents, boolean limitHost) { // 用正则表达式编译链接的匹配模式。 Pattern p =Pattern.compile("<img//s+src//s*=//s*/"?(.*?)[/"|>]",Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(pageContents); ArrayList< String> linkList = new ArrayList< String>(); while (m.find()) { String link = m.group(1).trim(); if (link.length() < 1) { continue; } // 跳过链到本页面内链接。 if (link.charAt(0) == '#') { continue; } if (link.indexOf("mailto:") != -1) { continue; } if (link.toLowerCase().indexOf("javascript") != -1) { continue; } if (link.toLowerCase().endsWith("gif")) { continue; } if (link.indexOf("://") == -1) { if (link.charAt(0) == '/') {//处理绝对地 link = "http://" + pageUrl.getHost()+":"+pageUrl.getPort()+ link; } else { String file = pageUrl.getFile(); if (file.indexOf('/') == -1) {//处理相对地址 link = "http://" + pageUrl.getHost()+":"+pageUrl.getPort() + "/" + link; } else { String path =file.substring(0, file.lastIndexOf('/') + 1); link = "http://" + pageUrl.getHost() +":"+pageUrl.getPort()+ path + link; } } } int index = link.indexOf('#'); if (index != -1) { link = link.substring(0, index); } link = removeWwwFromUrl(link); URL verifiedLink = verifyUrl(link); if (verifiedLink == null) { continue; } /* 如果限定主机,排除那些不合条件的URL*/ if (limitHost && !pageUrl.getHost().toLowerCase().equals( verifiedLink.getHost().toLowerCase())) { continue; } // 跳过那些已经处理的链接. // if (crawledList.contains(link)) { // continue; // } if(filterImg.contains(link)) { logEvent("图片匹配了:"+link); continue; } else { filterImg.add(link); } if(link.lastIndexOf(".gif")==-1) { linkList.add(link); } } return (linkList); } //执行实际的搜索操作 public ArrayList< String> crawl(String startUrl,boolean limithost,boolean caseSensitive ) { // 从开始URL中移出www startUrl = removeWwwFromUrl(startUrl); toCrawlList.add(startUrl); int idxPageParse=0; while (toCrawlList.size()>0) { try { idxPageParse++; // Get URL at bottom of the list. String url = toCrawlList.iterator().next(); ps.setIntUrl(ps.getIntUrl()+1); // Remove URL from the to crawl list. toCrawlList.remove(url); int intRetryPage=0; while (sempPage.availablePermits()<=0) { System.out.println("暂时没有空闲的网页分析线程,等待3秒再执行..."); try { intRetryPage++; if(intRetryPage==10) { logEvent("分析网页"+url+"超时"); sempPage.release(); break; } Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } ParsePage tempPageThread=new ParsePage(url); execPage.submit(tempPageThread); logEvent("开启网页分析线程"+idxPageParse); if(idxPageParse==1) { Thread.currentThread().sleep(30000); } }catch(Exception e) { e.printStackTrace(); } } blnFlag=false; logEvent("抓图完成......"); return result; } public static void logEvent(String strLog) { System.out.println( new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒").format(new Date(Calendar.getInstance().getTimeInMillis()))+"=====>"+strLog); } // 主函数 public static void main(String[] args) { if(args.length!=6) { System.out.println("Usage:java SearchCrawler startUrl maxUrl searchString"); return; } @SuppressWarnings("unused") String strLogPath=args[1]; SearchCrawler crawler = new SearchCrawler(args[0]); outdir=args[3]+"/pic"+new SimpleDateFormat("yyyyMMdd").format(new Date(Calendar.getInstance().getTimeInMillis()))+"/"; File f=new File(outdir); if(!f.exists()) { f.mkdir(); } execPage = Executors.newFixedThreadPool(30); execImg = Executors.newFixedThreadPool(300); seroutdir=args[4]; seroutdirimg=args[5]; ps=new PoCalSearch(); pd=new PoDownload(); try { if(UtilSeriz.readObject(seroutdir)!=null) { System.out.println(new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒").format(new Date(Calendar.getInstance().getTimeInMillis()))+"=====>"+"反序列化URL..."); filterUrl=(SimpleBloomFilter)UtilSeriz.readObject(seroutdir); } else { filterUrl=new SimpleBloomFilter(); } if(UtilSeriz.readObject(seroutdir)!=null) { System.out.println(new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒").format(new Date(Calendar.getInstance().getTimeInMillis()))+"=====>"+"反序列化图片..."); filterImg=(SimpleBloomFilter)UtilSeriz.readObject(seroutdirimg); } else { filterImg=new SimpleBloomFilter(); } } catch (Exception e) { e.printStackTrace(); } String strPic=args[3]+"/pic"+new SimpleDateFormat("yyyyMMdd").format(new Date(Calendar.getInstance().getTimeInMillis()))+".log"; try { bw=new BufferedWriter(new FileWriter(strPic,false)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } Thread search=new Thread(crawler); System.out.println( new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒").format(new Date(Calendar.getInstance().getTimeInMillis()))+"=====>"+"开始爬图..."); System.out.println("下载了图:"); search.start(); try { search.join(); logEvent("主函数结束"); bw.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 说明:下载图片的线程 * @author binbin0915 * */ public class ImgDownThread implements Runnable,Callable<Long>{ //待下载的URL private String stru; private boolean isStart=true; public ImgDownThread(String strurl) { super(); this.stru = strurl; } @Override public void run() { try { sempImg.acquire(); try{ URL url=new URL(stru); BufferedInputStream in = new BufferedInputStream(url.openStream()); BufferedImage bi=ImageIO.read(url.openStream()); //尺寸要求 if (bi==null|| bi.getWidth()<30 || bi.getHeight()<30 ) { in.close(); return; } String ss=new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(Calendar.getInstance().getTimeInMillis()))+"_"+Math.round(Math.random()*89999999999999L+1000)+stru.substring(stru.lastIndexOf(".")); String s=outdir+ss; FileOutputStream file = new FileOutputStream(new File(s)); int t; while ((t = in.read()) != -1) { file.write(t); } file.close(); if(new File(s).length()<=10*1024) { in.close(); new File(s).delete(); return; } synchronized(bw) { String str=ss+":"+stru; bw.write(str); bw.newLine(); bw.flush(); } logEvent("下载了:"+stru); ps.setIntImg(ps.getIntImg()+1); in.close(); }catch(Exception e){ logEvent("**********************下载图片:"+stru+"超时"); } } catch (Exception e) { e.printStackTrace(); } finally{ sempImg.release(); } } public boolean isStart() { return isStart; } public void setStart(boolean isStart) { this.isStart = isStart; } @Override public Long call() throws Exception { try { sempImg.acquire(); try{ URL url=new URL(stru); BufferedInputStream in = new BufferedInputStream(url.openStream()); BufferedImage bi=ImageIO.read(url.openStream()); //尺寸要求 if (bi==null|| bi.getWidth()<30 || bi.getHeight()<30 ) { in.close(); return 0l; } String ss=new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(Calendar.getInstance().getTimeInMillis()))+"_"+Math.round(Math.random()*89999999999999L+1000)+stru.substring(stru.lastIndexOf(".")); String s=outdir+ss; FileOutputStream file = new FileOutputStream(new File(s)); int t; while ((t = in.read()) != -1) { file.write(t); } file.close(); if(new File(s).length()<=10*1024) { in.close(); new File(s).delete(); return 0l; } logEvent("下载了:"+stru); ps.setIntImg(ps.getIntImg()+1); in.close(); }catch(Exception e){ logEvent("**********************下载图片:"+stru+"超时"); } } catch (Exception e) { e.printStackTrace(); } finally{ sempImg.release(); return 1l; } } } /*** * 序列化已访问的URL * @author binbin0915 * */ public class TimeWrite2File implements Runnable { @Override public void run() { while(blnFlag) { try { synchronized(ps) { logEvent("开始序列化URL"); UtilSeriz.writeObject(filterUrl,seroutdir); logEvent("结束序列化URL"); logEvent("开始序列化图片"); UtilSeriz.writeObject(filterImg,seroutdirimg); logEvent("结束序列化图片"); logEvent("分析了"+ps.getIntUrl()+"个链接"); logEvent("下载了"+ps.getIntImg()+"张图片"); } Thread.sleep(600000); } catch (Exception e) { e.printStackTrace(); } } } } /*** * 分析对应URL网页的线程 * @author Administrator * */ class ParsePage extends Thread { String url; int iCount=0; public int getiCount() { return iCount; } public void setiCount(int iCount) { this.iCount = iCount; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public ParsePage(String url) { this.url=url; } @Override public void run() { try { sempPage.acquire(); // Convert string url to URL object. URL verifiedUrl = verifyUrl(url); // Skip URL if robots are not allowed to access it. if (!isRobotAllowed(verifiedUrl)) { Thread.currentThread().stop(); } // 增加已处理的URL到crawledList String pageContents=""; pageContents = downloadPage(verifiedUrl); logEvent("分析了:"+verifiedUrl); logEvent("待分析URL数:"+toCrawlList.size()+"个"); if (pageContents != null && pageContents.length() > 0) { // 从页面中获取有效的链接 ArrayList< String> links =retrieveLinks(verifiedUrl, pageContents,limitHost); // 从页面中获取有效的链接 ArrayList< String> imglinks =retrieveImgLinks(verifiedUrl, pageContents,limitHost); //添加到图片下载队列 if(toCrawlList.size()<100000) { toCrawlList.addAll(links); } else { logEvent("待分析的网页URL超过100000!!!!跳过......."); } for(int i=0;i<imglinks.size();i++) { if(imglinks.get(i).indexOf("http:")!=-1) { iCount++; filterImg.add(imglinks.get(i)); ps.setIntImg(ps.getIntImg()+1); int intRetryImg=0; while (sempImg.availablePermits() <= 0) { System.out.println("暂时没有空闲的抓图线程,等待3秒再执行..."); try { intRetryImg++; if(intRetryImg==10) { logEvent("抓图"+imglinks.get(i)+"超时"); sempImg.release(); } Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } Thread tempImgThread=new Thread(new ImgDownThread(imglinks.get(i))); execImg.submit(tempImgThread); if((iCount!=1) && (iCount%10==1) ) { try { logEvent("图多休息2秒......"); Thread.currentThread().sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } synchronized(arrPar) { arrPar.remove(this); } } catch(Exception e) { e.printStackTrace(); } finally { sempPage.release(); } } } }

Java抓图程序的实现(改进版)相关推荐

  1. 自己写的Java抓图程序

    公司里要写一个抓图的程序 其实和搜索引擎差不多的原理 下载分析网页遇到<a>标签继续模拟点击进去再分析该网页 遇到<img>就下载该图 难点: 1 URL去重 采用bloomf ...

  2. java程序ssh置顶_使用shell脚本启动远程(SSH)Java应用程序不会返回本地提示

    我见过类似的问题,所有已解决的问题已经解决/不适用. 我在启动Java应用程序的远程计算机中有一个bash脚本.相关的行将是: #!/usr/bin/env bash ... java -cp /fu ...

  3. java 正则 cpu 100_这六个原因真的可以使Java应用程序的CPU使用率飙升到100%吗?...

    点击上方的"代码农户的冥想记录",然后选择"设为明星" 高质量文章,及时交付 问题 1. 无限while循环会导致CPU使用率飙升吗? 2.经常使用Young ...

  4. java web程序示例_想要建立一些有趣的东西吗? 这是示例Web应用程序创意的列表。...

    java web程序示例 Interested in learning JavaScript? Get my ebook at jshandbook.com 有兴趣学习JavaScript吗? 在js ...

  5. java 用程序代码解释继承_关于初级java程序员笔试题

    关于初级java程序员笔试题 Sun 认证Java程序员考试内容涉及Java所有相关知识.编程概念及applet开发技巧.下面是小编整理的关于初级java程序员笔试题,欢迎大家参考! 第一题:判断题 ...

  6. [Google API](8)构建使用Google API的Java应用程序

    Google 搜索引擎建立起了通过 Web 服务接口可用的索引.拼写建议和缓存页面,从而允许所有语言的程序员都能就个人使用存取信息.Google 搜索引擎还提供了 Java API,从而存取数据更为便 ...

  7. 用JEP 343打包工具,构建自包含、可安装的Java应用程序

    OpenJDK社区发布了JEP 343:打包工具的早期访问版本.JEP 343:打包工具,又名jpackage,是打包自包含Java应用程序和Java运行时环境的新工具.这个基于JavaFX java ...

  8. Java应用程序项目的打包与发行

    这里主要是讲解一下怎样将 Java程序打包成独立运行的exe程序包,以下这种方法应该是最佳的解决方案了.NetDuke的EXE程序包了是使用这种方案制作的.在操作步骤上还是比较简单的,而且通用性强. ...

  9. 面向 Java 开发人员的 Ajax: 构建动态的 Java 应用程序

    面向 Java 开发人员的 Ajax: 构建动态的 Java 应用程序 Ajax 为更好的 Web 应用程序铺平了道路 在 Web 应用程序开发中,页面重载循环是最大的一个使用障碍,对于 Java™ ...

  10. Java高级程序员(5年左右)面试的题目集

    Java高级程序员(5年左右)面试的题目集 https://blog.csdn.net/fangqun663775/article/details/73614850?utm_source=blogxg ...

最新文章

  1. XmlHttp学习笔记
  2. python配置文件密码管理_python – 可以在django管理员中实现“下次登录时更改密码”类型功能吗?...
  3. 关闭页面刷新上层页面的几种方式
  4. Win10修改防火墙入站规则
  5. 文本显示变量_【RPA课堂】UiPath中的变量、数据类型和组件
  6. 文件上传java逻辑_Java 文件上传 实例
  7. 关于Js下拉导航的解释
  8. 安装虚拟机Centos系统并安装Docker过程记录
  9. 重构职场竞争力之测试跨界思维
  10. 使用Elizabeth为您的应用程序生成随机数据
  11. 使用 Linux下 timerfd 系列 API 创建定时器并使用 epoll 监听
  12. Tcl 语言 ——语法篇
  13. 分享110个ASP源码,有一款适合你
  14. 【MacOS】MacOS 添加虚拟打印机
  15. 百度披露被黑原委 黑客骗得邮箱
  16. 使用google.zxing制作条形码和二维码
  17. 重置微信内置浏览器字体大小
  18. 身为UI设计师,如何应对失业的恐慌
  19. mysql查询重名_同名同姓搜索,同名身份证号码数据库
  20. 推荐系统三十六式(刑无刀)学习笔记(四)

热门文章

  1. uniapp ios 跳转appstore
  2. 编译udf小软件(附视频教程)
  3. 计算机毕业设计项目推荐(源码+论文+PPT)
  4. DoublyLinkedList
  5. checking for libzip... configure: error: system libzip must be upgraded to version = 0.11问题解决
  6. 人工智能数学基础8:两个重要极限及夹逼定理
  7. 卡瓦莱斯的世界杯往事
  8. Android View事件分发机制
  9. a标签的href属性与事件修饰符阻止默认行为
  10. 分段线性插值法实验报告_试验二插值法(含实验报告格式)-金锄头文库