注意: 如果你希望你的站点能通过 <你的 GitHub 用户名>.github.io 域名访问,你的 repository 应该直接命名为 <你的 GitHub 用户名>.github.io。
注意: 如果你希望你的站点能通过 <你的 GitHub 用户名>.github.io 域名访问,你的 repository 应该直接命名为 <你的 GitHub 用户名>.github.io。
cnpm install --save hexo-deployer-git
还是去你自己创建的文件夹中找到_config.yml文件,在文件的最后,加入 如下内容
deploy: type: git repo: 第一步创建的仓库地址 branch: master
例如
deploy: type: git repo: https://github.com/TomorrowLi/TomorrowLi.github.io branch: master
这里注意,type , repo, branch 后面都有一个空格。
将你创建的文件夹推送到github上,首先你肯定得进入到你的文件夹下,如我的是bolg,输入以下命令即可。
hexo ghexo d
耐心等待即可。完成后可以到刚刚创建的仓库。
接下来你就可以通过你的仓库名字直接访问你的个人博客网站。不需要通过localhost:4000访问了。
如:https://tomorrowli.github.io/
至此,你就拥有一个自己的博客网站了。快来试试吧。
]]>package com.semaphore;import java.util.concurrent.Exchanger;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ExchangerTest { private static final Exchanger<String> exgr = new Exchanger<String>(); private static ExecutorService threadPool = Executors.newFixedThreadPool(2); public static void main(String[] args) { threadPool.execute(new Runnable() { @Override public void run() { try { String A = "银行流水A";// A录入银行流水数据 System.out.println("本来是" + A + "变成了" + exgr.exchange(A)); } catch (InterruptedException e) { } } }); threadPool.execute(new Runnable() { @Override public void run() { try { String B = "银行流水B";// B录入银行流水数据 System.out.println("本来是" + B + "变成了" + exgr.exchange(B)); } catch (InterruptedException e) { } } }); threadPool.shutdown(); }}
]]>当然,thread的join方法实现多线程顺序执行有两种方式,一种是在子线程内部调用join()方法,另一种是直接在主线程调用join()方法;写法上是主线程上调用join()更直观,谁先执行谁后执行一目了然。子线程内部调用的话,相对的要注意调用的顺序。
1.子线程内部调用join()
public class ThreadJoinDemo { public static void main(String[] args) throws InterruptedException { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("打开冰箱!"); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("拿出一瓶牛奶!"); } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("关上冰箱!"); } }); //下面三行代码顺序可随意调整,程序运行结果不受影响,因为我们在子线程中通过“join()方法”已经指定了运行顺序(还有一个原因就是之前说的多线程的生命周期中有就绪状态,需要等待cpu调度,start并不意味着运行状态)。 thread3.start(); thread2.start(); thread1.start(); }}
2.在主线程中通过join()方法指定顺序
public class ThreadMainJoinDemo { public static void main(String[] args) throws InterruptedException { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("打开冰箱!"); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println("拿出一瓶牛奶!"); } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { System.out.println("关上冰箱!"); } }); thread1.start(); thread1.join(); thread2.start(); thread2.join(); thread3.start(); }}
public class ThreadPoolDemo { static ExecutorService executorService = Executors.newSingleThreadExecutor(); public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("打开冰箱!"); } }); final Thread thread2 =new Thread(new Runnable() { @Override public void run() { System.out.println("拿出一瓶牛奶!"); } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { System.out.println("关上冰箱!"); } }); executorService.submit(thread1); executorService.submit(thread2); executorService.submit(thread3); executorService.shutdown(); //使用完毕记得关闭线程池 }}
CountDownLatch类的特点就是可以初始化设置一个state的值,每次调用countDown()方法进行减一,如果state没有减小到0就会被await()方法一直阻塞。利用CountDownLatch方法可以实现和join()方法子线程运用一样的妙处。当然,CountDownLatch类的缺点是state不能重置,只能初始化一次,意思就是减小到0后,就没用了。。。不能加回去。
public class ThreadCountDownLatchDemo { private static CountDownLatch countDownLatch1 = new CountDownLatch(1); private static CountDownLatch countDownLatch2 = new CountDownLatch(1); public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("打开冰箱!"); countDownLatch1.countDown(); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { countDownLatch1.await(); System.out.println("拿出一瓶牛奶!"); countDownLatch2.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { countDownLatch2.await(); System.out.println("关上冰箱!"); } catch (InterruptedException e) { e.printStackTrace(); } } }); //下面三行代码顺序可随意调整,程序运行结果不受影响 thread3.start(); thread1.start(); thread2.start(); }}
]]>百度网盘下载
ReadFileThread
public class ReadFileThread extends Thread { private ReaderFileListener processPoiDataListeners; private String filePath; private long start; private long end; public ReadFileThread(ReaderFileListener processPoiDataListeners,long start,long end,String file) { this.setName(this.getName()+"-ReadFileThread"); System.out.println(this.getName()); this.start = start; this.end = end; this.filePath = file; this.processPoiDataListeners = processPoiDataListeners; } @Override public void run() { ReadFile readFile = new ReadFile(); readFile.setReaderListener(processPoiDataListeners); readFile.setEncode(processPoiDataListeners.getEncode()); // readFile.addObserver(); try { readFile.readFileByLine(filePath, start, end + 1); } catch (Exception e) { e.printStackTrace(); } }
}
ReadFile
import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.Observable; /** * @author: LiMing * @since: 2022/5/27 17:43 **/ public class ReadFile extends Observable { private int bufSize = 1024; // 换行符 private byte key = "\n".getBytes()[0]; // 当前行数 private long lineNum = 0; // 文件编码,默认为gb2312 private String encode = "gb2312"; // 具体业务逻辑监听器 private ReaderFileListener readerListener; public void setEncode(String encode) { this.encode = encode; } public void setReaderListener(ReaderFileListener readerListener) { this.readerListener = readerListener; } /** * 获取准确开始位置 * @param file * @param position * @return * @throws Exception */ public long getStartNum(File file, long position) throws Exception { long startNum = position; FileChannel fcin = new RandomAccessFile(file, "r").getChannel(); fcin.position(position); try { int cache = 1024; ByteBuffer rBuffer = ByteBuffer.allocate(cache); // 每次读取的内容 byte[] bs = new byte[cache]; // 缓存 byte[] tempBs = new byte[0]; String line = ""; while (fcin.read(rBuffer) != -1) { int rSize = rBuffer.position(); rBuffer.rewind(); rBuffer.get(bs); rBuffer.clear(); byte[] newStrByte = bs; // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面 if (null != tempBs) { int tL = tempBs.length; newStrByte = new byte[rSize + tL]; System.arraycopy(tempBs, 0, newStrByte, 0, tL); System.arraycopy(bs, 0, newStrByte, tL, rSize); } // 获取开始位置之后的第一个换行符 int endIndex = indexOf(newStrByte, 0); if (endIndex != -1) { return startNum + endIndex; } tempBs = substring(newStrByte, 0, newStrByte.length); startNum += 1024; } } catch (Exception e) { e.printStackTrace(); } finally { fcin.close(); } return position; } /** * 从设置的开始位置读取文件,一直到结束为止。如果 end设置为负数,刚读取到文件末尾 * @param fullPath * @param start * @param end * @throws Exception */ public void readFileByLine(String fullPath, long start, long end) throws Exception { File fin = new File(fullPath); if (fin.exists()) { FileChannel fcin = new RandomAccessFile(fin, "r").getChannel(); fcin.position(start); try { ByteBuffer rBuffer = ByteBuffer.allocate(bufSize); // 每次读取的内容 byte[] bs = new byte[bufSize]; // 缓存 byte[] tempBs = new byte[0]; String line = ""; // 当前读取文件位置 long nowCur = start; while (fcin.read(rBuffer) != -1) { nowCur += bufSize; int rSize = rBuffer.position(); rBuffer.rewind(); rBuffer.get(bs); rBuffer.clear(); byte[] newStrByte = bs; // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面 if (null != tempBs) { int tL = tempBs.length; newStrByte = new byte[rSize + tL]; System.arraycopy(tempBs, 0, newStrByte, 0, tL); System.arraycopy(bs, 0, newStrByte, tL, rSize); } // 是否已经读到最后一位 boolean isEnd = false; // 如果当前读取的位数已经比设置的结束位置大的时候,将读取的内容截取到设置的结束位置 if (end > 0 && nowCur > end) { // 缓存长度 - 当前已经读取位数 - 最后位数 int l = newStrByte.length - (int) (nowCur - end); newStrByte = substring(newStrByte, 0, l); isEnd = true; } int fromIndex = 0; int endIndex = 0; // 每次读一行内容,以 key(默认为\n) 作为结束符 while ((endIndex = indexOf(newStrByte, fromIndex)) != -1) { byte[] bLine = substring(newStrByte, fromIndex, endIndex); line = new String(bLine, 0, bLine.length, encode); lineNum++; // 输出一行内容,处理方式由调用方提供 readerListener.outLine(line.trim(), lineNum, false); fromIndex = endIndex + 1; } // 将未读取完成的内容放到缓存中 tempBs = substring(newStrByte, fromIndex, newStrByte.length); if (isEnd) { break; } } // 将剩下的最后内容作为一行,输出,并指明这是最后一行 String lineStr = new String(tempBs, 0, tempBs.length, encode); readerListener.outLine(lineStr.trim(), lineNum, true); } catch (Exception e) { e.printStackTrace(); } finally { fcin.close(); } } else { throw new FileNotFoundException("没有找到文件:" + fullPath); } // 通知观察者,当前工作已经完成 setChanged(); notifyObservers(start+"-"+end); } /** * 查找一个byte[]从指定位置之后的一个换行符位置 * * @param src * @param fromIndex * @return * @throws Exception */ private int indexOf(byte[] src, int fromIndex) throws Exception { for (int i = fromIndex; i < src.length; i++) { if (src[i] == key) { return i; } } return -1; } /** * 从指定开始位置读取一个byte[]直到指定结束位置为止生成一个全新的byte[] * * @param src * @param fromIndex * @param endIndex * @return * @throws Exception */ private byte[] substring(byte[] src, int fromIndex, int endIndex) throws Exception { int size = endIndex - fromIndex; byte[] ret = new byte[size]; System.arraycopy(src, fromIndex, ret, 0, size); return ret; } }
ReaderFileListener:
// 一次读取行数,默认为500 private int readColNum = 500; private String encode; private List<String> list = new ArrayList<String>(); /** * 设置一次读取行数 * @param readColNum */ protected void setReadColNum(int readColNum) { this.readColNum = readColNum; } public String getEncode() { return encode; } public void setEncode(String encode) { this.encode = encode; } /** * 每读取到一行数据,添加到缓存中 * @param lineStr 读取到的数据 * @param lineNum 行号 * @param over 是否读取完成 * @throws Exception */ public void outLine(String lineStr, long lineNum, boolean over) throws Exception { if(null != lineStr) list.add(lineStr); if (!over && (lineNum % readColNum == 0)) { output(list); list.clear(); } else if (over) { output(list); list.clear(); } } /** * 批量输出 * * @param stringList * @throws Exception */ public abstract void output(List<String> stringList) throws Exception;
ProcessDataByPostgisListeners
import java.util.List; /** * @author: LiMing * @since: 2022/5/30 17:51 **/ public class ProcessDataByPostgisListeners extends ReaderFileListener { @Override public void output(List<String> stringList) throws Exception { for (String s : stringList) { System.out.println(s); } } }
BuildData
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class BuildData { public static void main(String[] args) throws Exception { File file = new File("D:\\Users\\TomorrowLi\\Desktop\\log文件\\92020701_out.log.2022-05-23.16.40.15"); FileInputStream fis = null; try { ReadFile readFile = new ReadFile(); fis = new FileInputStream(file); int available = fis.available(); int maxThreadNum = 5; //核心线程 int corePoolSize=5; //最大线程数 int maxnumPoolSize=10; //等待时间 long keepAliveTime=1; // 线程粗略开始位置 int i = available / maxThreadNum; ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maxnumPoolSize,keepAliveTime, TimeUnit.MINUTES,new SynchronousQueue<>()); for (int j = 0; j < maxThreadNum; j++) { // 计算精确开始位置 long startNum = j == 0 ? 0 : readFile.getStartNum(file, i * j); long endNum = j + 1 < maxThreadNum ? readFile.getStartNum(file, i * (j + 1)) : -2; // 具体监听实现 ReaderFileListener listeners = new ProcessDataByPostgisListeners(); listeners.setEncode("UTF-8"); threadPoolExecutor.submit(new ReadFileThread(listeners, startNum, endNum, file.getPath())); } threadPoolExecutor.shutdown(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } }
HashMap底层数据结构 JDK1.7:数组+链表 JDK1.8:数组+链表+红黑树。每个数组都会有Key-value的值。java7是Entry,java8是Node。当链表长度大于8并且数组的长度大于64会转换为红黑树。
put
1)通过hash(Object key)算法得到hash值;
2)判断table是否为null或者长度为0,如果是执行resize()进行扩容;
3)通过hash值以及table数组长度得到插入的数组索引i,判断数组table[i]是否为空或为null;
4)如果table[i] == null,直接新建节点添加,转向 8),如果table[i]不为空,转向 5);
5)判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,这里的相同指的是hashCode以及equals,否则转向 6);
6)判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转7);
7)遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
8)插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
get
我们先简单说一下get(Object key)流程,通过传入的key通过hash()算法得到hash值,在通过(n - 1) & hash找到数组下标,如果数组下标所对应的node值正好key一样就返回,否则找到node.next找到下一个节点,看是否是treenNode,如果是,遍历红黑树找到对应node,如果不是遍历链表找到node
共同点1.容量(capacity):容量为底层数组的长度,JDK7中为Entry数组,JDK8中为Node数组 a. 容量一定为2的次幂2.加载因子(Load factor):HashMap在其容量自动增加前可达到多满的一种尺度 a. 默认加载因子 = 0.753.扩容机制:扩容时resize(2 * table.length),扩容到原数组长度的2倍。4.key为null:若key == null,则hash(key) = 0,则将该键-值 存放到数组table 中的第1个位置,即table [0]不同点1.发生hash冲突时 JDK7:发生hash冲突时,新元素插入到链表头中,即新元素总是添加到数组中,就元素移动到链表中。 JDK8:发生hash冲突后,会优先判断该节点的数据结构式是红黑树还是链表,如果是红黑树,则在红黑树中插入数据;如果是链表,则将数据插入到链表的尾部并判断链表长度是否大于8,如果大于8要转成红黑树。2.扩容时 JDK7:在扩容resize()过程中,采用单链表的头插入方式,在将旧数组上的数据 转移到 新数组上时,转移操作 = 按旧链表的正序遍历链表、在新链表的头部依次插入,即在转移数据、扩容后,容易出现链表逆序的情况 。 多线程下resize()容易出现死循环。此时若(多线程)并发执行 put()操作,一旦出现扩容情况,则 容易出现 环形链表,从而在获取数据、遍历链表时 形成死循环(Infinite Loop),即 死锁的状态 。
在jdk1.8中对HashMap进行了优化,在发生hash碰撞,不再采用头插法方式,而是直接插入链表尾部,因此不会出现环形链表的情况,但是在多线程的情况下仍然不安全,这里我们看jdk1.8中HashMap的put操作源码:
原因有两点:1.加快哈希运算 2.减少哈希冲突1.加快哈希运算我们都知道比如向hashMap中存入一个值,通常做法是对这个值求hashCode()得到一个数hash,然后在用hash对集合长度求余数,也就是 hash%length=positon得到的结果就是存放的位置。但是求余%的运算效率比较低。有没有更快的运算呢?答案是使用&运算。但是使用&运算怎么样才能和使用%效果一样呢?那就是,当HashMap的长度为2的幂的时候一下公式就成立了:hash%length==hash&(length-1)。所以就可以使用&运算来求位置下标了。2.减少哈希冲突,保证数据分散使用2的幂为长度,则length-1后为奇数,该奇数转为2进制后最后一位肯定是1。假如长度为4,则长度-1为3,再转为2进制==0000011,该值与任何hash做&运算都会形成==奇数==或者==偶数==两种情况,保证数据时分散的。可能有人会想这有什么用?那么我们假如长度不是4而是3,则3-1为2,再转为2进制==0000010,该值与任何hash做&运算都会形成==偶数==,那也就是说我的奇数的下标都不能用了。这样就不仅浪费一般的空间,而且增加了hash冲突的概率.
为什么负载因子是0.75也是一个综合考虑,如果设置过小,HashMap每put少量的数据,都要进行一次扩容,而扩容操作会消耗大量的性能。如果设置过大的话,如果设成1,容量还是16,假设现在数组上已经占用的15个,再要put数据进来,计算数组index时,发生hash碰撞的概率将达到15/16,这违背的HashMap减少hash碰撞的原则。
1.DEFAULT_INITIAL_CAPACITY缺省table大小(也就是说table长度为指定时table的默认值)2.MAXIMUM_CAPACITYtable最大长度3.DEFAULT_LOAD_FACTOR缺省负载因子大小(默认为0.75)4.TREEIFY_THRESHOLD=8树化阈值(也就是说table的node中的链表长度超过这个阈值的时候,该链表会变成树)5.UNTREEIFY_THRESHOLD=6树降级成为链表的阈值(也就是说table的node中的树长度低于这个阈值的时候,树会变成链表)6.MIN_TREEIFY_CAPACITY=64树化的另一个参数,就是当hashmap中的node的个数大于这个值的时候,hashmap中的有些链表才会变成树。这里纠正一个误区,并不是说hashmap中的某个node链表长度大于8就一定会变成树,而是说整个hashmap的node数量大于64,node的链表长度大于8才会变成树
1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//key如果是null 新hashcode是0 否则 计算新的hashcode}
]]>引用计数算法(Reference Counting)
在对象(对象头)中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一; 当引用失效时,计数器值就减一; 任何时刻计数器为零的对象就是不可能再被使用的。引用计数算法虽然占用了一些额外的内存空间来进行计数,但它的原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。这个看似简单的算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。
可达性分析算(Reachability Analysis)
当前主流的商用程序语言(Java、C#)的内存管理子系统,都是通过可达性分析算法来判定对象是否存活的。这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的
在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:
1. 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
2. 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
3. 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
标记-清除(Mark-Sweep)算法:
如它的名字一样,算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存 活的对象,统一回收所有未被标记的对象。标记过程就是对象是否属于垃圾的判定过程
它的主要缺点有两个: 第一个是执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增⻓而降低; 第二个是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作(Full GC)。
标记-复制算法:
为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销,但对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况, 只要移动堆顶指针,按顺序分配即可。这样实现简单,运行高效,不过其缺陷也显而易⻅,这种复制回收算法的代价是将可用内存缩小为了原来的一半,空间浪费未免太多了一点。
标记-整理(Mark-Compact)算法:
标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行,这就更加让使用者不得不小心翼翼地权衡其弊端了,像这样的停顿被最初的虚拟机设计者形象地描述为“Stop The World”(STW)。
Stop The World
因为GC时,尽可能要让垃圾回收器专心工作,不能随便让我们写的Java系统继续新建对象了,所以此时JVM会在后台直接进入“Stop the World”:停止Java系统所有工作线程,让我们写的代码无法再运行!然后让GC线程能安心执行GC。这就能让我们的系统暂停运行,不再创建新对象,同时让GC线程尽快完成GC工作:标记和转移Eden及Survivor2的存活对象到Survivor1,然后快速地一次性回收掉Eden和Survivor2中的垃圾对象:GC完毕,即可继续恢复Java系统的工作线程运行,继续在Eden创建新对象:
经过两年的停更,今天决定重新开始。这两年中由于种种原因,不能继续下去。有过快乐,有过悲伤。有舍有得。总之受益良多。加油!!!
]]>import reimport urllib.requestfrom selenium import webdriverfrom selenium.common.exceptions import TimeoutExceptionfrom selenium.webdriver.support.ui import WebDriverWaitfrom multiprocessing import Poolbrowser = webdriver.Chrome()wait=WebDriverWait(browser, 10)def search(url,next_title): try: browser.get(url) html = browser.page_source reg = r'<span style="" id="streamurj">(.*?)</span>' wang = re.findall(reg, html) for i in wang: zhongzi='https://openload.co/stream/' +i print(zhongzi) with open('lianjie.txt', 'a+') as f: f.write(str(zhongzi) + '\n') f.close() #load(product['url'],product['title']) except TimeoutException: return search(url,next_title)def load(url,title): print('正在下载',url) urllib.request.urlretrieve(url,'mp4/%s.mp4'% title) print('下载成功',title)def index_page(url): try: browser.get(url) html = browser.page_source reg = r'<td class="mytd-padding"><a href="(.*?)">' video = re.findall(reg, html) for age in video: next_url = 'http://ourjav.com/' + age.split('[')[0] next_title=age.split('[')[-1] next_page(next_url,next_title) except TimeoutException: index_page(url)def next_page(url,next_title): try: browser.get(url) html = browser.page_source age=r'<iframe id="flash_game_object" name="flash_game_object" frameborder="no" scrolling="no" width="650" height="500" src="(.*?)" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>' video=re.findall(age,html,re.S) for image in video: search(image,next_title) except TimeoutException: next_page(url, next_title)def read_load(): f2 = open("lianjie.txt","r") lines = f2.readlines() count=0 for line3 in lines: print(line3) respon=browser.get(line3) count=count+1 if count==5: break print(count)def main(offset): url = 'http://ourjav.com/'+'index.php?&page=%s' % offset index_page(url)if __name__ == '__main__': group = [x for x in range(1, 11)] pool = Pool() pool.map(main, group)
其次从txt文件中读取url进行下载
from selenium import webdriverfrom selenium.webdriver.support.ui import WebDriverWaitbrowser = webdriver.Chrome()wait=WebDriverWait(browser, 10)def read_load(): f2 = open("lianjie.txt","r") lines = f2.readlines() count=0 for line3 in lines: print(line3) browser.get(line3) count=count+1 if count==5: break print(count)def main(): read_load()if __name__ == '__main__': main()
]]>requests
data={ 'limit':20, 'offset':20 }
#!/usr/bin/python3# -*- coding: utf-8 -*-# @Time : 2018/3/13 15:40# @Author : tomorrowliimport requestsclass Zhuhu():#初始化参数def __init__(self): #赋值网址 self.url = 'https://zhuanlan.zhihu.com/api/columns/wajuejiprince/followers' self.headers={ 'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36' }def getMesg(self,param): self.html=requests.get(self.url,params=param,headers=self.headers) for m in range(20): ''' with open('zhihu.txt','a') as f: print(self.html.json()[m]['hash']) f.write(self.html.json()[m]['hash']+'\n') ''' data={ 'bio':self.html.json()[m]['bio'], 'name':self.html.json()[m]['name'], 'profileUrl':self.html.json()[m]['profileUrl'], 'hash':self.html.json()[m]['hash'] } print(data)def main(): for n in range(0,60,20): data={ 'limit':20, 'offset':n } zhihu=Zhuhu() zhihu.getMesg(data)if __name__ == '__main__': main()
需要获取每个粉丝的hash和自己cookie值
]]>import reimport requestsdef fand_email(url,counts): data=requests.get(url) content=data.text pattern = r'[0-9a-zA-Z._]+@[0-9a-zA-Z._]+\.[0-9a-zA-Z._]+' p = re.compile(pattern) m = p.findall(content) with open('emal.txt','a+') as f: for i in m: f.write(i+'\n') print(i) counts= counts+1 return countsdef main(): counts=0 numbers=0 for i in range(1,32): url='http://tieba.baidu.com/p/2314539885?pn=%s'% i number=fand_email(url,counts) numbers=numbers+number print(numbers)if __name__ == '__main__': main()
]]>re、selenium、pymysql
首先在自己的python路径下放入 chromedriver.exe 选择自己浏览器合适的版本下载
from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECimport reimport MySQLdbhost='localhost'#主机名user='root'#数据库的用户名passwd=''#数据库的密码db='test'#数据库的名字conn=MySQLdb.connect(host,user,passwd,db,charset='utf8')cursor = conn.cursor()boser=webdriver.Chrome()boser.maximize_window()wait=WebDriverWait(boser , 10)def getlogin(): boser.get('https://www.tianyancha.com/login') user = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "#web-content > div > div > div > div.position-rel.container.company_container > div > div.in-block.vertical-top.float-right.right_content.mt50.mr5.mb5 > div.module.module1.module2.loginmodule.collapse.in > div.modulein.modulein1.mobile_box.pl30.pr30.f14.collapse.in > div.pb30.position-rel > input")) ) password=wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, '#web-content > div > div > div > div.position-rel.container.company_container > div > div.in-block.vertical-top.float-right.right_content.mt50.mr5.mb5 > div.module.module1.module2.loginmodule.collapse.in > div.modulein.modulein1.mobile_box.pl30.pr30.f14.collapse.in > div.pb40.position-rel > input')) ) submit = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR , '#web-content > div > div > div > div.position-rel.container.company_container > div > div.in-block.vertical-top.float-right.right_content.mt50.mr5.mb5 > div.module.module1.module2.loginmodule.collapse.in > div.modulein.modulein1.mobile_box.pl30.pr30.f14.collapse.in > div.c-white.b-c9.pt8.f18.text-center.login_btn')) ) user.send_keys('用户名') password.send_keys('密码') submit.click() getSearch()def getSearch(): user = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR , "#home-main-search")) ) submit = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR , '#web-content > div > div.mainV3_tab1_enter.position-rel > div.mainv2_tab1.position-rel > div > div.main-tab-outer > div:nth-child(2) > div > div:nth-child(1) > div.input-group.inputV2 > div > span')) ) key='佛山市顺德区' user.send_keys(key) submit.click() html=boser.page_source geturl(html) getUrlNext()def getUrlNext(): for i in range(2,5): boser.get(r'https://www.tianyancha.com/search/p%s?key=佛山市顺德区'% i) html=boser.page_source geturl(html)def geturl(html): try: reg=r'<a href=".*?" target="_blank" tyc-event-click="" tyc-event-ch="CompanySearch.Company" style="word-break: break-all;font-weight: 600;" class="query_name sv-search-company f18 in-block vertical-middle"><span>(.*?)</span></a>' names=re.findall(reg,html,re.S) reg=r'<a title=".*?" class="legalPersonName hover_underline" target="_blank" href=".*?">(.*?)</a>' daibiao=re.findall(reg,html,re.S) reg=r'<div class="title overflow-width" style="border-right: none">.*?<span title=".*?">(.*?)</span></div>' days = re.findall(reg,html,re.S) reg=r'<span class="sec-c3">联系电话:</span><span class="overflow-width over-hide vertical-bottom in-block" style="max-width:500px;">(.*?)</span>' phones = re.findall(reg , html , re.S) reg=r'<div class="add"><span class="sec-c3">.*?</span><span>:</span><span class="overflow-width over-hide vertical-bottom in-block" style="max-width:500px;">(.*?)</span>' location=re.findall(reg,html,re.S) for i in range(20): data={ 'name':names[i].replace('<em>','').replace('</em>',''), 'daibiao':daibiao[i], 'day':days[i].replace('<em>','').replace('</em>',''), 'phone':phones[i].replace('<em>','').replace('</em>',''), 'location': location[i].replace('<em>', '').replace('</em>','') } print(data) cursor.execute("insert into tianyancha(name,daibiao,day,phone,location) values('{}','{}','{}','{}','{}')".format(data['name'],data['daibiao'],data['day'],data['phone'],data['location'])) print(data['daibiao'],'成功存入数据库') conn.commit() except: return Nonedef main(): getlogin()if __name__ == '__main__': main()
结果展示
]]>第一种方式:使用默认无参构造函数(最为常用) <!--在默认情况下: 它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。 <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
第二种方式:spring 管理静态工厂-使用静态工厂的方法创建对象 /** * 模拟一个静态工厂,创建业务层实现类 */ public class StaticFactory { public static IAccountService createAccountService(){ return new AccountServiceImpl(); } } <!-- 此种方式是: 使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器 id 属性:指定 bean 的 id,用于从容器中获取 class 属性:指定静态工厂的全限定类名 factory-method 属性:指定生产对象的静态方法 --> <bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="createAccountService"></bean>第三种方式:spring 管理实例工厂-使用实例工厂的方法创建对象 /** * 模拟一个实例工厂,创建业务层实现类 * 此工厂创建对象,必须现有工厂实例对象,再调用方法 */ public class InstanceFactory { public IAccountService createAccountService(){ return new AccountServiceImpl(); } } <!-- 此种方式是: 先把工厂的创建交给 spring 来管理。 然后在使用工厂的 bean 来调用里面的方法 factory-bean 属性:用于指定实例工厂 bean 的 id。 factory-method 属性:用于指定实例工厂中创建对象的方法。 --> <bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean> <bean id="accountService" factory-bean="instancFactory" factory-method="createAccountService"></bean>
第一种方式:构造函数注入 <!-- 使用构造函数的方式,给 service 中的属性传值 要求: 类中需要提供一个对应参数列表的构造函数。 涉及的标签: constructor-arg 属性: index:指定参数在构造函数参数列表的索引位置 type:指定参数在构造函数中的数据类型 name:指定参数在构造函数中的名称 用这个找给谁赋值 =======上面三个都是找给谁赋值,下面两个指的是赋什么值的============== value:它能赋的值是基本数据类型和 String 类型 ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="张三"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <bean id="now" class="java.util.Date"></bean>第二种方式:set 方法注入 <!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式 涉及的标签: property 属性: name:找的是类中 set 方法后面的部分 ref:给属性赋值是其他 bean 类型的 value:给属性赋值是基本数据类型和 string 类型的 实际开发中,此种方式用的较多。 --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="name" value="test"></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property> </bean> <bean id="now" class="java.util.Date"></bean>
]]>作用: 在程序运行期间,不修改源码对已有方法进行增强。 优势: 减少重复代码 提高开发效率 维护方便
提供者:JDK 官方的 Proxy 类。 要求:被代理类最少实现一个接口。
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。要求:被代理类不能用 final 修饰的类(最终类)。
]]>import reimport MySQLdbfrom selenium import webdriverfrom selenium.common.exceptions import TimeoutExceptionfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom pyquery import PyQuery as pqbroser=webdriver.Chrome()wait=WebDriverWait(broser, 10)def search(table): try: broser.get('https://www.taobao.com') inputs = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "#q")) ) submit = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_TSearchForm > div.search-button > button')) ) inputs.send_keys('美食') submit.click() total = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR,'#mainsrp-pager > div > div > div > div.total')) ) get_product(table) return total.text except TimeoutException: return search(table)def next_page(page_number,table): try: inputs = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")) ) submit = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")) ) inputs.clear() inputs.send_keys(page_number) submit.click() wait.until( EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)) ) get_product(table) except TimeoutException: next_page(page_number,table)def get_product(table): wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#mainsrp-itemlist .items .item'))) html=broser.page_source doc=pq(html) items=doc('#mainsrp-itemlist .items .item').items() for item in items: product={ 'image':item.find('.pic .img').attr('src'), 'price':item.find('.price').text().replace('\n',''), 'deal':item.find('.deal-cnt').text()[:-3], 'title':item.find('.title').text().replace('\n',''), 'shop':item.find('.shop').text(), 'location':item.find('.location').text() } print(product) inserttable(table, product['image'], product['price'], product['deal'], product['title'],product['shop'],product['location'])#连接数据库 mysqldef connectDB(): host="localhost" dbName="test" user="root" password="" #此处添加charset='utf8'是为了在数据库中显示中文,此编码必须与数据库的编码一致 db=MySQLdb.connect(host,user,password,dbName,charset='utf8') return db cursorDB=db.cursor() return cursorDB#创建表,SQL语言。CREATE TABLE IF NOT EXISTS 表示:表createTableName不存在时就创建def creatTable(createTableName): createTableSql="CREATE TABLE IF NOT EXISTS "+ createTableName+"(image VARCHAR(255),price VARCHAR(255),deal VARCHAR(255),title VARCHAR(255),shop VARCHAR(255),location VARCHAR(255))" DB_create=connectDB() print('链接数据库成功') cursor_create=DB_create.cursor() cursor_create.execute(createTableSql) DB_create.close() print('creat table '+createTableName+' successfully') return createTableName#数据插入表中def inserttable(insertTable,insertimages,insertprice,insertdeal,inserttitle,insertshop,insertloaction): insertContentSql="INSERT INTO "+insertTable+"(image,price,deal,title,shop,location)VALUES(%s,%s,%s,%s,%s,%s)"# insertContentSql="INSERT INTO "+insertTable+"(time,title,text,clicks)VALUES("+insertTime+" , "+insertTitle+" , "+insertText+" , "+insertClicks+")" DB_insert=connectDB() cursor_insert=DB_insert.cursor() cursor_insert.execute(insertContentSql,(insertimages,insertprice,insertdeal,inserttitle,insertshop,insertloaction)) DB_insert.commit() DB_insert.close() print ('inert contents to '+insertTable+' successfully')def main(): table = creatTable('yh1') print('创建表成功') total=search(table) total=int(re.compile('(\d+)').search(total).group(1)) for i in range(2,total+1): next_page(i,table)if __name__=='__main__': main()
结果展示
]]>1、resquests2、re
二、创建一个scrapy
scrapy startproject ArticleSpiderscrapy genspider jobbole www.jobbole.com
三、爬取所有文章列表的url以及爬取下一页
#所要爬取链接的起始urlstart_urls = ['http://blog.jobbole.com/all-posts/']def parse(self, response): #获取第一页下的所有a标签 url_list=response.css('#archive .floated-thumb .post-thumb a') for url in url_list: #获取img标签下的src属性,图片的链接dizhi url_img=url.css('img::attr(src)').extract_first() #获取每一个url_list的详情页面的url urls=url.xpath('@href')[0].extract() #通过parse.urljoin传递一个绝对地址 #通过meta属性向item中添加font_image_url属性 yield scrapy.Request(parse.urljoin(response.url,urls),meta={'font_image_url':url_img},callback=self.get_parse) #获取下一页的链接地址、 next=response.css('.next.page-numbers::attr(href)')[0].extract() #一直循环,知道没有下一页为止 if next: #回调parse函数 yield scrapy.Request(parse.urljoin(response.url,next),callback=self.parse) else: return None
四、对详情文章页面进行分析
我们要的数据是文章的
[标题,日期,标签,文章,点赞数,收藏数,评论数]#标题title= response.css('.grid-8 .entry-header h1::text')[0].extract()#日期data=response.css('.grid-8 .entry-meta p::text')[0].extract().strip().replace('·','').strip()#标签tag_list = response.css("p.entry-meta-hide-on-mobile a::text").extract()tag_list = [element for element in tag_list if not element.strip().endswith("评论")]#删除标签内的评论数tags = ",".join(tag_list)#把标签数组以','链接生成字符串#文章article= response.css('.grid-8 .entry')[0].extract()#点赞数votetotal=response.css('.post-adds h10::text')[0].extract()match_re = re.match('.*(\d+).*', votetotal)#用正则筛选出我们所需要的数字if match_re: votetotal=int(match_re.group(1))#返回第一个else: votetotal=0#如国没有则默认为0#收藏数bookmark=response.css('.post-adds .bookmark-btn::text')[0].extract()match_re = re.match('.*(\d+).*', bookmark) if match_re: bookmark=int(match_re.group(1))else: bookmark=0#评论数comments=response.css('.post-adds a .hide-on-480::text')[0].extract()match_re = re.match('.*(\d+).*', comments)if match_re: comments=int(match_re.group(1))else: comments=0
get_parse方法来获取详情页的数据
def get_parse(self,response): #接收传过来的 font_iamgge_url image_url=response.meta.get('font_image_url','') title= response.css('.grid-8 .entry-header h1::text')[0].extract() data=response.css('.grid-8 .entry-meta p::text')[0].extract().strip().replace('·','').strip() category=response.css('.grid-8 .entry-meta p a::text')[0].extract() tag=response.css('.grid-8 .entry-meta p a::text')[-1].extract().strip().replace('·','').strip() article= response.css('.grid-8 .entry')[0].extract() votetotal=response.css('.post-adds h10::text')[0].extract() match_re = re.match('.*(\d+).*', votetotal) if match_re: votetotal=int(match_re.group(1)) else: votetotal=0 bookmark=response.css('.post-adds .bookmark-btn::text')[0].extract() match_re = re.match('.*(\d+).*', bookmark) if match_re: bookmark=int(match_re.group(1)) else: bookmark=0 comments=response.css('.post-adds a .hide-on-480::text')[0].extract() match_re = re.match('.*(\d+).*', comments) if match_re: comments=int(match_re.group(1)) else: comments=0 #对item对象实例化 item=ArticlespiderItem() item['url']=response.url #调用md5把url压缩为固定的哈希值 item['url_object_id'] = get_md5(response.url) item['image_url']=[image_url] #调用dtaetime库把字符串转化为date属性 try: data=datetime.datetime.strftime(data,"%Y/%m/%d").date() except Exception as e: #如果有异常就获取当前系统的时间 data=datetime.datetime.now().date() item['data']=data item['title'] = title item['category'] = category item['tag'] = tag item['article'] = article item['votetotal'] = votetotal item['bookmark']=bookmark item['comments'] = comments
五、下载每一篇文章的图片
在settings.py文件中加入#获取item中iamge_url的图片链接IMAGES_URLS_FIELD='image_url'#获取当前的文件路径object_url=os.path.abspath(os.path.dirname(__file__))#创建image文件夹来存储图片IMAGES_STORE=os.path.join(object_url,'image')
在image文件下会自动生成图片的名字,在ImagesPipeline中我们会找到path变量,我们可以找到每个url所对应的图片的名字,把它存到item中
class ArticleImagePipeline(ImagesPipeline):def item_completed(self, results, item, info): if 'image_url' in item: for ok,value in results: image_file_path=value['path'] item['image_url_path']=image_file_path return item
六、随着以后爬取速度加快,存的速度赶不上爬的速度,导致存的堆积影响性能,所以使用twisted将musql变成异步操作
from twisted.enterprise import adbapiclass MysqlTwistePipline(object):def __init__(self,dbpool): self.dbpool=dbpool@classmethoddef from_settings(cls,settings): dbparms=dict( host=settings['MYSQL_HOST'], db=settings['MYSQL_DB'] , user=settings['MYSQL_USER'] , passwd=settings['MYSQL_PASSWORD'] , charset='utf8', cursorclass=MySQLdb.cursors.DictCursor, use_unicode=True, ) dbpool=adbapi.ConnectionPool('MySQLdb',**dbparms) return cls(dbpool)def process_item(self , item , spider): #使用twisted将musql变成异步操作 query=self.dbpool.runInteraction(self.do_insert,item) query.addErrback(self.hand_erro)def hand_erro(self,failure): print(failure)def do_insert(self,cursor,item): url = item['url'] url_object_id = item['url_object_id'] image_urls = item['image_url'] image_url = image_urls[0] image_url_path = item['image_url_path'] title = item['title'] data = item['data'] category = item['category'] tag = item['tag'] article = item['article'] votetotal = item['votetotal'] bookmark = item['bookmark'] comments = item['comments'] cursor.execute( 'insert into jobole(title,data,url,url_object_id,image_url,image_url_path,tag,category,article,votetotal,bookmark,comments) values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)' , (title , data , url , url_object_id , image_url , image_url_path , tag , category , article , votetotal , bookmark , comments))
]]>1、pandas//是python的一个数据分析包2、matplotlib//是一个 Python 的 2D绘图库,它以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形。3、jieba//jieba(结巴)是一个强大的分词库,完美支持中文分词4、wordcloud//基于Python的词云生成类库5、numpy//NumPy系统是Python的一种开源的数值计算扩展。这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表(nested list structure)结构要高效的多(该结构也可以用来表示矩阵(matrix))
二、用scrapy创建一个项目
scrapy startproject zufangSpiderscrapy genspider zufang http://zu.sh.fang.com/cities.aspx
三、实现房天下的的数据爬取
#初始化urldef start_requests(self): yield scrapy.Reques(self.city_url,callback=self.get_city)#调用get_city()方法 def get_city(self,response): url=response.xpath('/html/body/div[3]/div[1]/a/@href').extract() #循环热门城市 for i in url: product={ 'city':i } yield scrapy.Request(product['city'],callback=self.city_parse, dont_filter=True) #循环爬取每一页 for j in range(2,10): next_url=product['city']+'house/i3%s'%j yield scrapy.Request(next_url,callback=self.city_parse,dont_filter=True)#调用city_parse()方法获取每一页的数据def city_parse(self, response): zufang = response.xpath('//div[@class="houseList"]') for fangzi in zufang: title=fangzi.xpath('//p[@class="title"]/a/text()').extract() area =fangzi.xpath('//p[@class="gray6 mt20"]/a[1]/span[1]/text()').extract() rent_style = fangzi.xpath('//p[@class="font16 mt20 bold"]/text()[1]').extract() house_type= fangzi.xpath('//p[@class="font16 mt20 bold"]/text()[2]').extract() house_area = fangzi.xpath('//p[@class="font16 mt20 bold"]/text()[3]').extract() if fangzi.xpath('//p[@class="font16 mt20 bold"]/text()[4]'): orientation = fangzi.xpath('//p[@class="font16 mt20 bold"]/text()[4]').extract() else: orientation='' price = fangzi.xpath('//p[@class="mt5 alingC"]/span/text()').extract() for i in range(len(title)): item = ZufangScrapyItem() item['title']=title[i] item['area']=area[i] item['rent_style']=rent_style[i].strip() item['house_type']=house_type[i] item['house_area']=house_area[i] item['orientation']=orientation[i].strip() item['price']=price[i] yield item
四、对数据进行可视化
1、首先运行 scrapy crawl zufang -o zufang.csv把数据保存成csv文件2、import pandas as pddata=pd.read_csv(r'H:\Python\zufang_scrapy\zufang.csv')data.head()
3、import matplotlib.pyplot as pltplt.rcParams['font.sans-serif']=['SimHei']data['orientation'].value_counts().plot(kind='barh',rot=0)plt.show()
4、final=''stopword=['NaN']for n in range(data.shape[0]):seg_list=list(jieba.cut(data['area'][n]))for seg in seg_list: if seg not in stopword: final=final+seg+' 'my_wordcloud=WordCloud(collocations=False,font_path=r'C:\Windows\Fonts\simfang.ttf',width=2000,height=600,margin=2).generate(final)plt.imshow(my_wordcloud)plt.axis('off')plt.show()
详细代码:可以访问我的 GitHub 地址
]]># Obey robots.txt rules#允许爬取robots.txt文件内不能爬取的资源ROBOTSTXT_OBEY = True#知乎是由反扒措施的必须添加User-agent以及authorizationDEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36' , 'authorization':'Bearer 2|1:0|10:1521707334|4:z_c0|80:MS4xS1NYS0FnQUFBQUFtQUFBQVlBSlZUVWEzb0Z0ZDl0MGhaa0VJOWM0ODhiejhRenVUd0tURnFnPT0=|98bb6f4900c1b4b52ece0282393ede5e31046c9a90216e69dec1feece8086ee0'}
二、编写spider的项目文件
1、zhihu_user.py
# -*- coding: utf-8 -*-import jsonimport scrapyfrom scrapy import Spider,Requestfrom ..items import UserItemclass ZhihuUserSpider(Spider): name = 'zhihu_user' allowed_domains = ['www.zhihu.com'] start_urls = ['http://www.zhihu.com/'] start_user='excited-vczh' user_url='https://www.zhihu.com/api/v4/members/{user}?include={include}' user_query='allow_message,is_followed,is_following,is_org,is_blocking,employments,answer_count,follower_count,articles_count,gender,badge[?(type=best_answerer)].topics' follows_url='https://www.zhihu.com/api/v4/members/{user}/followees?include={include}&offset={offset}&limit={limit}' follows_query='data[*].answer_count,articles_count,gender,follower_count,is_followed,is_following,badge[?(type=best_answerer)].topics' followers_url='https://www.zhihu.com/api/v4/members/{user}/followers?include={include}&offset={offset}&limit={limit}' followers_query='data[*].answer_count,articles_count,gender,follower_count,is_followed,is_following,badge[?(type=best_answerer)].topics' def start_requests(self): yield Request(self.user_url.format(user=self.start_user,include=self.user_query),self.parse_user) yield Request(self.follows_url.format(user=self.start_user,include=self.follows_query,offset=0,limit=20),callback=self.parse_query) def parse_user(self, response): result=json.loads(response.text) item=UserItem() for field in item.fields: if field in result.keys(): item[field]=result.get(field) yield item yield Request(self.follows_url.format(user=result.get('url_token'),include=self.follows_query,offset=0,limit=20),callback=self.parse_query) yield Request(self.followers_url.format(user=result.get('url_token'),include=self.followers_query,offset=0,limit=20),callback=self.parse_querys) def parse_query(self, response): results=json.loads(response.text) if 'data' in results.keys(): for result in results.get('data'): yield Request(self.user_url.format(user=result.get('url_token'),include=self.user_query),callback=self.parse_user) if 'paging' in results.keys() and results.get('paging').get('is_end')==False: next_page=results.get('paging').get('next') yield Request(next_page,self.parse_query) def parse_querys(self, response): results=json.loads(response.text) if 'data' in results.keys(): for result in results.get('data'): yield Request(self.user_url.format(user=result.get('url_token'),include=self.user_query),callback=self.parse_user) if 'paging' in results.keys() and results.get('paging').get('is_end')==False: next_page=results.get('paging').get('next') yield Request(next_page,self.parse_querys)
2、items.py
# -*- coding: utf-8 -*-# Define here the models for your scraped items## See documentation in:# https://doc.scrapy.org/en/latest/topics/items.htmlimport scrapyfrom scrapy import Item,Fieldclass UserItem(Item): # define the fields for your item here like: # name = scrapy.Field() headline=Field() avatar_url=Field() name=Field() type=Field() url_token=Field() user_type=Field()
3、pipelines.py
# -*- coding: utf-8 -*-# Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.htmlimport pymongoclass MongoPipeline(object): collection_name = 'scrapy_items' def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls( mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DATABASE') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): self.db['user'].update({'url_token':item['url_token']},{'$set':item},True) #self.db[self.collection_name].insert_one(dict(item)) return item
4、setting.py
BOT_NAME = 'zhihu'SPIDER_MODULES = ['zhihu.spiders']NEWSPIDER_MODULE = 'zhihu.spiders'MONGO_URI='localhost'MONGO_DATABASE='zhihu'
三、最后启动setting.py中的
ITEM_PIPELINES = { 'zhihu.pipelines.MongoPipeline': 300,}#去掉注释
四、结果展示
]]>1、安装wheel pip install wheel2、安装lxml https://pypi.python.org/pypi/lxml/4.1.03、安装pyopenssl https://pypi.python.org/pypi/pyOpenSSL/17.5.04、安装Twisted https://www.lfd.uci.edu/~gohlke/pythonlibs/5、安装pywin32 https://sourceforge.net/projects/pywin32/files/6、安装scrapy pip install scrapy
二、爬虫举例
1、创建项目
scrapy startproject zhihu
2、创建spider项目程序
cd zhihuscrapy genspider zhihuspider www.zhihu.com
3、自动创建目录及文件
4、生成的爬虫具有基本的结构,我们可以直接在此基础上编写代码
# -*- coding: utf-8 -*-import scrapyclass ZhihuSpider(scrapy.Spider):name = "zhihuspider"allowed_domains = ["zhihu.com"]start_urls = ['http://www.zhihu.com/']def parse(self, response): pass
5、然后,可以我们按照name来运行爬虫
scrapy crawl 'zhihuspider'
二、项目文件详细分析
1、spriders文件夹
#项目文件,以后的代码都要在这里写入zhihuspider.py
2、items.py
from scrapy import Item,Fieldclass UserItem(Item): #Item 对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like) 的API以及用于声明可用字段的简单语法。 # define the fields for your item here like: #爬取所需要的数据的名称在这里定义,很像字典 # name = scrapy.Field()
3、pipelines.py
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理
###主要是处理以下4点任务
1.清理HTML数据
2.验证爬取的数据(检查item包含某些字段)
3.查重(并丢弃)
4.将爬取结果保存到数据库中
#存到mogodb数据库import pymongoclass MongoPipeline(object): collection_name = 'scrapy_items' def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls( mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DATABASE', 'items') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): self.db[self.collection_name].insert_one(dict(item)) return item
4、settings.py
用于设置常量的,例如 mongodb 的 url 和 database
BOT_NAME = 'zhihu'SPIDER_MODULES = ['zhihu.spiders']NEWSPIDER_MODULE = 'zhihu.spiders'MONGO_URI='localhost'MONGO_DATABASE='zhihu'#默认是True的这个变量是用来是否爬robot文件的改为False是允许的意思ROBOTSTXT_OBEY = True
]]>有一部分网站(甚至包括部分商业网站)其实并没有真正认真做过反爬虫这件事情,你不需要做任何伪装,即可分分钟抓光它的网站。面对这样的网站,你只需要开大马力抓抓抓就好了,如果处于情怀考虑,你可以抓的慢一点,让它的服务器别太累。
除去这样的情况,最简单的反爬虫机制,应该算是U-A校验了。浏览器在发送请求的时候,会附带一部分浏览器及当前系统环境的参数给服务器,这部分数据放在HTTP请求的header部分。
这种反爬虫机制的原理是,真人通过浏览器访问网站的速度(相对程序来讲)是很慢的,所以如果你一秒访问了200个页面——这里不代表实际数值,只是表达在较短时间内发送了较多请求,具体阈值需要具体考虑——服务器则认为你是一个爬虫。
适用情况:限制频率情况。
Requests,Urllib2都可以使用time库的sleep()函数:
import timetime.sleep(1)
这样的方式与上面的方式相比更加难处理的是,不管你的访问频次怎么样,你都需要输入验证码才行。比如12306,不管你是登录还是购票,也不论你是第一次还是第一万次购买,你都需要输入一个验证码之后才能继续。这时候,在绝大部分情况下,你必须要想办法识别验证码了。之所以说是大多数情况下,是因为在极少数极少数情况下(尤其是政府网站),棒槌程序员通过客户端的JavaScript来校验验证码,这时候对你的爬虫来讲,其实是没有验证码的(比如中国商标网)。除开你遇到这种几乎可以忽略不计的棒槌开发的网站,其他时候你只有通过识别验证码来继续后面的操作了。
1、首先你可以找到验证吗图片的url
2、把图片保存到本地,然后手动输入验证码达到验证的效果,可以参考我前面的文章,有关豆瓣验证码的处理
常见于一些社交网站,他们会经常更换网页的结构,如果你是通过依赖网页结构来解析需要的数据(不幸的是大部分情况下我们都需要通过网页结构来解析需要的数据),则在原本的网页位置找不到原本需要的内容。这时候,要分不同情况具体处理了。如果你只打算一次性抓取特定数据,那么赶快行动,它以后结构变了无所谓,反正你也不打算在抓它一次。如果是需要持续性抓取的网站,就要仔细思考下应对方案了。一个简单粗暴但是比较费力的办法是,写一个校验脚本,定期校验网页结构,如果与预期不符,那么赶快通知自己(或者特定开发者)修改解析部分,以应对网页结构变化。
对于“加载更多”情况,使用Ajax来传输很多数据。它的工作原理是:从网页的url加载网页的源代码之后,会在浏览器里执行JavaScript程序。这些程序会加载更多的内容,“填充”到网页里。这就是为什么如果你直接去爬网页本身的url,你会找不到页面的实际内容。这里,若使用Google Chrome分析”请求“对应的链接(方法:右键→审查元素→Network→清空,点击”加载更多“,出现对应的GET链接寻找Type为text/html的,点击,查看get参数或者复制Request URL),循环过程。
Selenium是一款自动化测试工具。它能实现操纵浏览器,包括字符填充、鼠标点击、获取元素、页面切换等一系列操作。总之,凡是浏览器能做的事,Selenium都能够做到。
适用情况:限制IP地址情况,也可解决由于“频繁点击”而需要输入验证码登陆的情况。
这种情况最好的办法就是维护一个代理IP池,网上有很多免费的代理IP,良莠不齐,可以通过筛选找到能用的。对于“频繁点击”的情况,我们还可以通过限制爬虫访问网站的频率来避免被网站禁掉。
proxies = {'http':'http://XX.XX.XX.XX:XXXX'} Requests://强烈推荐使用 import requests response = requests.get(url=url, proxies=proxies) Urllib2: import urllib2 proxy_support = urllib2.ProxyHandler(proxies) opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler) urllib2.install_opener(opener) # 安装opener,此后调用urlopen()时都会使用安装过的opener对象 response = urllib2.urlopen(url)
由于Python的是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。
multiprocessing提供模块一个了Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:
from multiprocessing import Process
import os
#子进程要执行的代码def run_proc(name): print 'Run child process %s (%s)...' % (name, os.getpid())if __name__=='__main__': print 'Parent process %s.' % os.getpid() p = Process(target=run_proc, args=('test',)) print 'Process will start.' p.start() p.join() print 'Process end.'
执行结果如下:
Parent process 928.Process will start.Run child process test (929)...Process end.
参考 廖雪峰 的python教程
]]>