前情提要:Python爬虫初体验(2):多线程的应用及爬取中的实际问题
从来没想过,写一个功能较为完备的爬虫代码,要花好几天的时间……
这次算是增长了许多编程经验。
好的废话不多说,进入正题
上次的代码中,由于部分XKCD漫画有特殊格式(还有 404 彩蛋),而我的代码中 try-except 结构只处理了连接超时的问题,没有对漫画本身的格式变化进行处理,导致线程意外中断。虽然最后写了重试下载,但是它和上面的一样,有个致命问题:如果是漫画格式的问题,它会陷入无限死循环。
所以这次对以上问题进行了规避式处理:将所有步骤写进 try 里面。并且,代码仍然采用重试机制,如果重试仍然有错误出现,那么记录这个漫画编号,程序运行结束后手动下载这些特殊的漫画。
……
那么这次的新知识,就是所谓的线程锁和线程同步。
线程锁:使用 mutex = threading.Lock() 将线程锁的变量赋值给 mutex,之后在 mutex 中分别用 acquire() 和 release() 方法来上锁/解锁。这样做是为了避免几个线程同时访问变量造成混乱。
调用 acquire() 后,接下来代码中的变量将只能由当前线程访问,其余线程会被阻塞,直到使用 release(),其余线程才能访问这些变量。例如,我要对全局变量 count 进行读取和 +1 操作,不上锁时,可能第 1,2 个线程在 +1 时,第 3,4 个线程在读取,就会造成数据不统一的问题。所以这种情况一定要上锁确保访问安全。
线程同步:如果我们希望子线程全部结束后,能继续执行主线程,那么需要用到 join() 方法。
def func_1():
# do something_1
thread_list = []
for i in range(5):
thread_list.append(threading.Thread(target=func_1))
thread_list[i].start()
for i in range(5):
thread_list[i].join()
# do something_2
对于线程 t,调用 t.join() 使得主线程在这个线程执行完之后才继续进行下一步操作。
(知识参考了liudemeng:Python中线程与互斥锁这篇文章)
然后!
还是一样的方法去搞……
哎。几天下来都没弄好,不知道这次又会怎样……
……
果然还是出问题了qwq
前两个线程倒是下载完了,后面三个线程居然在中途全部卡死,这是什么意思???
最后一直卡在 874 号漫画上……然后就没动静了……
……
经过我一番分析,原来有一行下载的代码写的是
picres = requests.get("http:" + picurl)
第一次写爬虫的时候就提到了:不能直接使用 requests.get(),以防超时卡死!!!
这里疏忽了没有改,导致后三个线程下载中途均出现了 Read Timeout(读取超时)然后卡死的现象
然后一直卡在那里,程序就这样 GG 咯 (0.0)\\
……
哎!第四次了。这次应该会成功吧。
为了加快速度,把线程数从 5 改成了 6;为了得到不能批量下载的漫画编号,最后记录了一个 log 文件,记下了编号和总耗时。
……
……
Nice,这次的程序终于没问题了!并且几个特殊漫画的编号也保存了下来
最后看到,耗时接近 1 小时;2173张/h 的速度,相比于原来 300张/h (50张漫画 10分钟)的速度要提高了不少。一个是因为启用了多线程,另一个是读取超时的时间从原来的 10s 变为了 4s。
简单总结一下。
这次的爬虫体验收获还是很大,不仅是学到的知识得到了运用,同时发现了许多新的问题,特别是之前没有考虑过的问题:例如读取超时的处理,错误类型的判断,错误的处理,意外发生时的应对策略,等等。
于是,耗时 5 天的这个爬虫终于走到了一个新的里程碑处。开心!
下一步,可以再深挖一下多线程的应用,也可以开始准备图像处理的练习了。事情还是很多的。
……
最近生活上还需要多多调整心态。可以想想那些比我强的同学是怎么做的。
……
今天又拖到了 11 点过,说好的 11 点睡觉呢!!
不说了赶紧滚去睡觉~