最近写一个小的爬虫,对python的多线程稍微了解了一下,做个笔记以作备忘。
这个小爬虫做的事情,分为以下简单几步:
- 定向爬取某网站的数据
- 对得到的网页数据进行解析过滤出所需要的数据
- 将过滤出的数据存储到mysql中
这里先将以上步骤分解成几个函数:
def get_urls():
"""获取需要爬取的url地址列表"""
pass
def get_data(url):
"""爬取对应url下的页面数据"""
pass
def store_data(title,content):
"""将获取到的数据插入到mysql中,这里模拟title,data"""
pass
这里我将上面的几个函数存为spliter.py文件与下面的几个文件存在同一个文件夹下,方便调用。
先写一个单线程的方法
这是很多人一开始就会去完成的方法,相当的简单
import spliter
for url in spliter.get_urls():
title,content = spliter.get_data()
spliter.store_data(title,content)
上面的例子虽然很简单就可以把你所需要的数据从网上抓取下来,但是问题也是显而易见的,如果你抓取的网页比较多的话速度是相当慢的,再以前不晓得怎么写多线程的时候可以等上个1天也不为少,但用theading就可以解决这个时间问题。
用theading来实现多线程import threading,spliter,time
def thread(url):
title,content = spliter.get_data(url)
spliter.store_data(title,content)
for url in spliter.get_urls():
t = threading.Thread(target=thread, kward={'url':url})
t.start()
while threading.activeCount() > 1: time.sleep(1)
这是最基本的多线程的例子,但是这个例子明显有一个问题,有多少我要爬取的url就创建多少个线程,这样会带来下面的几个问题:
- 如果你同时发送1000+的请求到同一台服务器的话,和Dos这台服务器没什么两样,后果是你会被拒绝服务。
- 你电脑的性能受到限制,这么多线程会把你的内存吃光。
- 饿。。这么多虫子过去人家路由器也被你搞挂了,这样子无疑就太暴力了,试过douban.com 的,爬了一次就被拒绝服务了,冏。
下面简单的改进一下,设置并发的线程数:
import threading,spliter,time
THREAD_LIMIT = 20
def thread(url):
title,content = spliter.get_data(url)
spliter.store_data(title,content)
for url in spliter.get_urls():
while threading.activeCount() > THREAD_LIMIT:time.sleep(1)
t = threading.Thread(target=thread, kward={'url':url})
t.start()
while threading.activeCount() > 1: time.sleep(1)
改动后的代码是在for循环中又加了一层while循环,目的是让主线程挂起等待其他线程执行完成。
但这种方式有一个问题,我不断的创建新的线程然后仅仅存在很短的时间就将其销毁掉了,这种方式效率不高,我们可以用线程池来重新利用,来提高效率。
用线程池和队列进行改进import threading, spliter ,time , Queue
THREAD_LIMIT = 50
jobs = Queue.Queue(0)
def thread():
while True:
try:
url = jobs.get(False)
except Queue.Empty:
return
title,content = spliter.get_data(url)
spliter.store_data(title,content)
for url in spliter.get_urls():
jobs.put(url)
for n in xrange(THREAD_LIMIT):
t = threading.Thread(target=thread)
t.start()
while threading.activeCount() > 1: time.sleep(1)
上面改进的地方是:
- 线程永远只有你设置的50个,只是他们不停的被复用。
但也会带来问题:
- 譬如我是要把数据存入到数据中去,这样每个线程都会同时链接数据库,可能会带来大量的数据库锁定,可能还会造成数据库链接限额…
ONE WORKER, MANY RUNERSimport threading, spliter ,time , Queue
THREAD_LIMIT = 50
jobs = Queue.Queue(0)
process = Queue.Queue(LIMIT)
def thread():
while True:
try:
url = jobs.get(False)
except Queue.Empty:
return
title,content = spliter.get_data(url)
process.put((title,content),True)
if __name__ == '__main__':
for url in spliter.get_urls():
jobs.put(url)
for n in xrange(THREAD_LIMIT):
t = threading.Thread(target=thread)
t.start()
while threading.activeCount() > 1 or not process.empty():
try:
title,content = process.get(False,1)
except Queue.Empty:
continue
spliter.store_data(title,content)
以上作了一个简单的修改:新建了一个队列用于存储爬取返回的数据 ,如果数据量大的话可能会烧点内存,但是问题不大。
ok作个简单的笔记,因为平时python不是主业,就怕会忘记…..
转载于:https://blog.51cto.com/ylj798/1061907