爬取chinaz中的top域名

DNS TIAN 283℃ 0评论

两年前写的文章,哈哈。

现有一个小需求,爬取http://top.chinaz.com/all/index.html中的网站的域名,大概有1760页。

使用python的urllib2发出请求获取网页数据:

使用beautifulsoup解析html文档。

审查元素时发现要提取的域名的<span>标签的class属性均为”col-gray”,故搜索文档树:

或者也可以根据DOM的结构遍历文档树:

其中原本是想用.children遍历ul的文档树的,但是不知道为什么会出现NavigableString这样的element,于是使用更方便的find_all(True, recursive=False),其中True可以匹配任何值,但是不会返回字符串节点,recursive=False代表只想搜索该tag的直接子节点。

所以爬虫部分就写完了,可是这样的话整体速度会很慢,所以使用Python多线程进行并行处理。

Python中可以通过继承Thread类,重写它的run方法来创建线程,继承的话要在子类的构造函数中显示调用父类的构造函数:

创建多个线程的话,线程间的协作需要有一个“消息队列”,Python的Queue模块中提供了同步的、线程安全的队列类(注:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用)。

所以接下来把要爬取的网页都放入Queue中:

所以线程在Queue中提取要爬取的域名,就可以向myThread类中添加如下代码:

接下来就是最后一步了,将爬取的域名字符串都写入到同一个result.txt文件中,这里有一个多线程读写(资源竞争)的问题。有两种解决方法,其中一种方式是加“锁”。

当某一线程要进行写文件操作时,先申请锁,锁定方法acquire可以有一个超时时间的可选参数timeout。如果设定了timeout,则在超时后通过返回值可以判断是否得到了锁。完成后再进行释放。

在实际过程中还会有一个问题,就是爬虫爬取时会抛异常,比如访问过快等原因,此时需要捕获这个异常,所以需要try…exception,而且需要把之前的任务重新放回任务队列中:

当主线程新建一个文件,将句柄传入进子线程时,如果直接在主线程的末尾写了file.close(),那么当主线程在执行到该代码时该文件句柄就关闭了,这时就会有ValueError: I/O operation on closed file异常,解决办法之一是每次要写入文件时都打开文件、追加内容、关闭文件。除了这种方法外还可以在主线程的file.close()前做一次判断,判断子线程是不是都执行完了,如果是,那么再写入文件。代码如下:

至此任务就达到了,完整的代码如下:

另外在写入文件时,我还想到了一种方法,类似于生产者-消费者模型,采用写内存再延迟写硬盘的方案,多创建一个Queue用来存储结果,创建一个线程读取Queue中的内容然后写入文件,完整的代码如下。

这个跟上一个代码有一点点不同的是它没有用到互斥量(锁),而是新建了一个线程类专门用来写入文件。注意到结尾的最后几行用到了Queue.join()方法,它的意思是等到队列为空,再执行别的操作,它要配合Queue.task_done(),即当每次从队列中get到一个数据后,处理好相关问题就调用该方法,用来提示Queue.join()是否停止阻塞。在上面的代码中,由于URL队列首先为空,所以在所有URL爬取操作完毕后才会涉及到写入的阻塞清除。另外这么写的话线程write_thread不会退出,所以使用setDaemon(True)方法将线程设置为守护线程。

至此这么一个问题就暂时解决了,本文标题为爬虫教学,其实就是记录一下我的思考过程而已。

The End

转载请注明:老田博客 » 爬取chinaz中的top域名

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址