利用正则和bs4爬取sec-wiki(多线程)

运行了上一篇单线程文件后你会觉得爬取的很慢,这是因为上一篇的过程是线性的,单线程:

拿到第一页url->爬取这页所有详情页链接->获取第一个详情页面代码->爬取信息->获取第二个详情页代码->获取信息….->拿到第二页url…

这样是顺序执行的,速度肯定慢,cpu利用率也低,所以想要速度快让任务并发进行就需要多线程了,多线程的执行逻辑如下

1
2
3
4
5
6
main函数--|-->拿到第一页url->爬取详情页链接->获取第一个详情页面代码->爬取信息->写文件
      |-->拿到第二页url->爬取详情页链接->获取第二个详情页面代码->爬取信息->写文件
      |-->拿到第三页url->爬取详情页链接->获取第三个详情页面代码->爬取信息->写文件
      |-->拿到第四页url->爬取详情页链接->获取第四个详情页面代码->爬取信息->写文件
      |-->...
      |-->拿到第n页url->爬取详情页链接->获取第n个详情页面代码->爬取信息->写文件

现在主函数改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if __name__=="__main__":
threads = []
newsPage = "http://www.sec-wiki.com/news/index"
homeCode = getOnePage(newsPage,0)
pattern = re.compile(r'<li class="last"><a href="/news/index\?News_page=(.*?)">',re.S)
pageSum = re.findall(pattern,homeCode)
num = int(pageSum[0])
#num = 3 #因为有500多页,都爬取完时间太久,所以可以先爬3页看看效果
print pageSum
for i in range(num):#从0开始,需要+1
t = threading.Thread(target=getUrl,args=(i,))
threads.append(t)
for thread in threads:
thread.start()
sleep(1)
thread.join()

线程是根据获取的总共页数来启动的,每一个线程拿到了自己要去爬取哪一页用i来表示:t = threading.Thread(target=getUrl,args=(i,))
然后加入到线程列表中:threads.append(t)  

但是此时线程还没有启动需要用到接下来的for循环才能启动,并且调用线程的join函数。这个join函数的作用就是防止线程函数都启动起来后主线程会继续执行,因为主线程后面已经没有事情做了。如果让主线程继续走的话此程序的主线程就会退出,只要主线程退出,那么刚才所启动的所以子线程就会一同退出,这样我们的程序就停了,为了防止这件事情的发生,join函数就是干这件事的。  

那为什么要加一个sleep睡眠1秒呢,这是因为估计网站服务器那边有限制,如果我请求的过于频繁就会ban掉我的ip,所以每起1个线程我就休息1秒钟,这样就解决了问题。  

关于python的线程使用和python线程中join的用法请参考以下两个链接,简单明了:

这个多线程的版本还多干了一件事就是将所获取到的信息保存到文本文件中。在这里会遇到一件多线程中必须考虑的问题,就是锁的问题。
threading模块中给我们提供了两种类型的锁:threading.Lock和threading.RLock关于他们两个的区别请查看http://blog.sina.com.cn/s/blog_5dd2af0901012rad.html
 
我们这里是利用threading.RLock,在了解了为什么要用锁之后,那么锁的用法非常简单,只有三句话,如下:

1
2
3
4
5
import threading
mylock=threading.RLock()
mylock.acquire()
具体内容
mylock.release()

那我们这写文件为什么要用锁呢,我们的结果只保存到一个文件中,而每一个线程都要去将自己爬取到的结果保存到这个文件中,那么想象一下如果有两个进程同时打开了这个文件,进程1正在写文章标题,还没写完,这是进程2也将此文件打开了,我们这里打开文件的方式是追加方式,于是进程2就接着进程1还没写完的内容后面继续写,这样就造成了文件内容的错乱。于是锁的作用就体现出来了。

我们定义了一个全局的锁,我要去写文件了,就看看锁是否有人占用,如果没人占用,我就拿到锁的所有权,然后去文件中尽情的写,别人在我写的时候也想去写,但是他也需要拿到锁的所有权才能继续,而所有权现在被我占用,他就得等着,等我写完了,我将锁的所有权交出来,别人就可以用了。所以在打开文件之前先acquire()一下,写完之后release()一下。

其他内容和单线程的没有区别,这里不再赘述。

此文件github地址:https://github.com/lowkeynic4/crawl/blob/master/secwiki/secwiki-thread.py