热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python爬虫开发解析(三续):快速线程池爬虫网站安全分享!

本文算是填前面的一个坑,有朋友和我将我前面写了这么多,真正没看到什么特别突出的实战,给了应对各种情况的方案。多线程那里讲的也是坑。忽然想想,说的也对,为读者考虑我确实应该把多线程这


本文算是填前面的一个坑,有朋友和我将我前面写了这么多,真正没看到什么特别突出的实战,给了应对各种情况的方案。多线程那里讲的也是坑。忽然想想,说的也对,为读者考虑我确实应该把多线程这里的坑补完。
然后决定再以一篇文章的形式讲一下这个轻型线程池爬虫,同时也为大家提供一个思路。代码都是经过调试的,并且留了相对友好的用户接口。可以很容易得添加各种各样增强型的功能。
0×01 功能定义
1.  可选择的单页面爬虫与多页面线程池爬虫
2.  可定制对HTML的处理
3.  可定制获取HTML的方式(应对动态页面)
4.  当设置为非单页面爬虫时,自动启动对当前域名下所有的页面进行深度优先爬取
5.  自定义线程数
0×02 总体流程
Python爬虫开发解析(三-续):快速线程池爬虫
0×03 线程池任务迭代实现
虽然在上面图中写出了线程池的影子,但是我们还是需要单独拿出来写一下线程池的到底是怎么样工作的,以方便读者更好地理解源代码的内容。
Python爬虫开发解析(三-续):快速线程池爬虫
0×04 具体实现
到这里相信读者知道用线程池来完成我们需要完成的爬虫了吧。关于具体内容的实现,是接下来我们要讲的。
1.    依赖:
我们需要用到这五个模块,我相信大家都很熟悉,那么就不多介绍了,如果有朋友不熟悉的话可以翻到前面的文章重新复习一下。
threading
Queue
urlparse
requests
bs4
2.    类的声明:
ScraperWorkerBase这个类是完全可以复写的,只要和原有的接口保持一致,可以满足用户的各种各样的需求,例如,定义页面扫描函数需要复写parse方法(当然这些我是在后面会有实例给大家展示)
那么我们还需要介绍一下其他的接口:
Execute方法中控制主逻辑,用最简洁的语言和代码表现逻辑,如果有需要自定义自己的逻辑控制方法,那么务必保持第一个返回值仍然是inpage_url
__get_%ignore_a_1%_data控制获取html数据的方法,你可以自己定制headers,COOKIEs,post_data
__get_soup这个方法是以bs4模块来解析html文档
__get_all_url与__get_url_inpage不建议大家修改,如果修改了的话可能会影响主爬虫控制器的运行
然后我在这里做一张ScraperWorkerBase的流程图大家可以参考一下
Python爬虫开发解析(三-续):快速线程池爬虫
class ScraperWorkerBase(object):
    """
    No needs to learn how is work,
    rewrite parse_page using self.soup(Beautiful), and return result,
    you can get the result by using
   
        (inpage_urls, your_own_result) urlscraper.execute()
   
    But this class is default for scraper to use,
    To enhance its function , you can completement this class
    like:
   
    class MyWorker(ScraperWorkerBase):
   
        def parse_page(self):
            all_tags = self.soup.find_all('img')
            for i in all_tags:
                print i
   
    """
    def __init__(self, url = ''):
       
        self.target_url = url
        self.netloc = urlparse.urlparse(self.target_url)[1]
       
       
        self.response = None
        self.soup = None
       
        self.url_in_site = []
        self.url_out_site = []
       
    """override this method to get html data via any way you want or need"""   
    def __get_html_data(self):
        try:
            self.respOnse= requests.get(self.target_url, timeout = 5)
        except:
            return ""
       
        print "[_] Got response"
        return self.response.text

    def __get_soup(self):
        text = self.__get_html_data()
        if text == '':
            return []
       
        return bs4.BeautifulSoup(text)
    def __get_all_url(self):
        url_lists = []
       
        self.soup = self.__get_soup()
        if isinstance(self.soup, type(None)):
            return []
       
        all_tags = self.soup.findAll("a")
        for a in all_tags:
            try:
                #print a['href']
                url_lists.append(a["href"])
            except:
                pass
           
        return url_lists
   
    def get_urls_inpage(self):
        ret_list = self.__get_all_url()
       
        if ret_list == []:
            return ([],[])
        else:
            for url in ret_list:
                o = urlparse.urlparse(url)
                #
                #print url
                if self.netloc in o[1]:
                    self.url_in_site.append(o.geturl())
                else:
                    self.url_out_site.append(o.geturl())
                   
        inurlset = set(self.url_in_site)
           
        outurlset = set(self.url_out_site)
           
        return inurlset, outurlset
    def execute(self):
        inpage_url = self.get_urls_inpage()
        undefined_result = self.parse_page()
        return inpage_url, undefined_result
   
   
    """You can override this method to define your own needs"""
    def parse_page(self):
        pass
       
       
这个类定义了处理HTML页面的基本方法,如果需要仅仅是获取页面所有的超链接的话,那么最基础的Worker类已经替大家实现了,但是如果需要对某类网站特定元素进行处理,那么完全可以只复写parse_page
例如:
Python爬虫开发解析(三-续):快速线程池爬虫
如果要绕开网站的限制进行爬取数据,就需要复写:
Python爬虫开发解析(三-续):快速线程池爬虫
但是如果需要对特定url进行限制,最好不要去复写__get_all_url方法,而应该去复写get_urls_inpage方法
关于Scraper的类说明:
这个类显然没有前面的那么好理解,但是如果使用过HTMLParser或者是SGMLParser的读者,肯定是记得那个feed方法的。这与我们要介绍的这个类有一些相似的地方。

 

在这个类中,我们建立Scraper对象的时候,需要传入的参数直接决定了我们的线程池爬虫的类型:究竟要不要启动多线程,启动多少个线程,使用哪个处理函数来除了web页面?这些都是我们要考虑的问题。所以接下来我们对这些部分进行一些说明
这些设定很好理解,在__init__中输入是否是单页面爬虫模式,设定线程数,设定爬虫解析的具体类。然后对应初始化线程池:初始化的时候要生成多个_worker方法,循环工作,然后在_worker方法的工作时完成对传入的实际进行解析的ScraperWorkerBase类进行调用,然后收集结果填入任务队列。
通过feed的方法来添加目标url,可以输入list,也可以直接输入str对象。
当不想让Scraper再工作的时候,调用kill_workers就可以停止所有的worker线程。
但是仅仅是明白这个只是可能仅仅会使用而已,既然是开发我们肯定是要清楚地讲这个Scraper是怎么样被组织起来的,他是怎么样工作的。
首先第一个概念就是任务队列:我们feed进的数据实际就是把任务添加到任务队列中,然后任务分配的时候,每个爬虫都要get到属于自己的任务,然后各司其职的去做,互不干扰。
第二个类似的概念就是结果队列:结果队列毫无疑问就是用于存储结果的,在外部获取这个Scraper的结果队列以后,需要去获取结果队列中的元素,由于队列的性质,当结果被抽走的时候,被获取的结果就会被删除。
在大家明确了这两个概念以后,这个Scraper的工作原理接回很容易被理解了:
Python爬虫开发解析(三-续):快速线程池爬虫
当然这个图我在做的时候是有点小偷懒的,本来应该做两种类型的Scraper,因为实际在使用的过程中,Scraper在一开始要被指定为单页还是多页,但是为了避免大量的重复所以在作图的时候我就在最后做了一个逻辑判断来表明类型,来帮助大家理解这个解析过程。我相信一个visio流程图比长篇大论的文字解释要直观的多对吧?
那么接下来我们看一下爬虫的实体怎么写:
class Scraper(object):
    def __init__(self, single_page = True,  workers_num = 8, worker_class = ScraperWorkerBase):
        self.count = 0
        self.workers_num = workers_num
       
        """get worker_class"""
        self.worker_class = worker_class
       
        """check if the workers should die"""
        self.all_dead = False
       
        """store the visited pages"""
        self.visited = set()
       
        """by ScraperWorkerBase 's extension result queue"""
        self.result_urls_queue = Queue.Queue()
        self.result_elements_queue = Queue.Queue()
       
        """
        if single_page == True,
        the task_queue should store the tasks (unhandled)
        """
        self.task_queue = Queue.Queue()
       
        self.single_page = single_page
        if self.single_page == False:
            self.__init_workers()
        else:
            self.__init_single_worker()
       
    def __check_single_page(self):
        if self.single_page == True:
            raise StandardError('[!] Single page won't allow you use many workers')
       
    """init worker(s)"""
    def __init_single_worker(self):
        ret = threading.Thread(target=self._single_worker)
        ret.start()
    def __init_workers(self):
        self.__check_single_page()
       
        for _ in range(self.workers_num):
            ret = threading.Thread(target=self._worker)
            ret.start()

 

    """return results"""
    def get_result_urls_queue(self):
        return self.result_urls_queue
    def get_result_elements_queue(self):
        return self.result_elements_queue
     
    """woker function"""
    def _single_worker(self):
        if self.all_dead != False:
            self.all_dead = False
        scraper = None
        while not self.all_dead:
            try:
               
                url = self.task_queue.get(block=True)
                print 'Workding', url
                try:
                    if url[:url.index('#')] in self.visited:
                        continue
                except:
                    pass
               
                if url in self.visited:
                    continue
                else:
                    pass
                self.count = self.count+ 1
                print 'Having process', self.count , 'Pages'
                scraper = self.worker_class(url)
                self.visited.add(url)
                urlset, result_entity = scraper.execute()
                for i in urlset[0]:
                    #self.task_queue.put(i)
                    self.result_urls_queue.put(i)
               
                if result_entity != None:
                    pass
                else:
                    self.result_elements_queue.put(result_entity)
                   
            except:
                pass           
            finally:
                pass       
    def _worker(self):
        if self.all_dead != False:
            self.all_dead = False
        scraper = None
        while not self.all_dead:

 

            try:
               
                url = self.task_queue.get(block=True)
                print 'Workding', url
                try:
                    if url[:url.index('#')] in self.visited:
                        continue
                except:
                    pass
               
                if url in self.visited:
                    continue
                else:
                    pass
                self.count = self.count + 1
                print 'Having process', self.count , 'Pages'
                scraper = self.worker_class(url)
                self.visited.add(url)
                urlset, result_entity = scraper.execute()
                for i in urlset[0]:
                    if i in self.visited:
                        continue
                    else:
                        pass
                    self.task_queue.put(i)
                    self.result_urls_queue.put(i)
               
                if result_entity != None:
                    pass
                else:
                    self.result_elements_queue.put(result_entity)
                   
            except:
                pass           
            finally:
                pass
           
    """scraper interface"""
    def kill_workers(self):
        if self.all_dead == False:
            self.all_dead = True
        else:
            pass
    def feed(self, target_urls = []):
        if isinstance(target_urls, list):

 

            for target_url in target_urls:
                self.task_queue.put(target_url)
        elif isinstance(target_urls, str):
            self.task_queue.put(target_urls)
        else:
            pass
       
       
        #return url result
        return (self.get_result_urls_queue(), self.get_result_elements_queue() )
这些设定很好理解,在__init__中输入是否是单页面爬虫模式,设定线程数,设定爬虫解析的具体类。然后对应初始化线程池:初始化的时候要生成多个_worker方法,循环工作,然后在_worker方法的工作时完成对传入的实际进行解析的ScraperWorkerBase类进行调用,然后收集结果填入任务队列。
通过feed的方法来添加目标url,可以输入list,也可以直接输入str对象。
当不想让Scraper再工作的时候,调用kill_workers就可以停止所有的worker线程。
0×04 使用实例
下面是几个相对完整的使用实例:
单页面爬虫使用实例
#encoding:utf-8
from scraper import *
import Queue
import time
import sys
import bs4
test_obj = Scraper(single_page=True, workers_num=15)
test_obj.feed(['http://freebuf.com'])
time.sleep(5)
z = test_obj.get_result_urls_queue()
while True:
    try :
        print z.get(timeout=4)
    except:
        pass
线程池爬虫实例:
寻找一个网站下所有的url
#encoding:utf-8
from scraper import *
import Queue
import time
import sys
import bs4
test_obj = Scraper(single_page=False, workers_num=15)
test_obj.feed(['http://freebuf.com'])
time.sleep(5)
z = test_obj.get_result_urls_queue()
while True:
    try :
        print z.get(timeout=4)
    except:
        pass
我们发现和上面的单页面爬虫只是一个参数的区别。实际的效果还是不错的。
下面是自定义爬取方案的应用
Python爬虫开发解析(三-续):快速线程池爬虫
这样的示例代码基本把这个爬虫的目的和接口完整的展示出来了,用户可以在MyWorker中定义自己的处理函数。
0×05 测试使用
在实际的使用中,这个小型爬虫的效果还是相当不错的,灵活,简单,可扩展性高。有兴趣的朋友可以给它配置更多的功能型组件,比如数据库,爬取特定关键元素,针对某一个页面的数据处理。比如在实际的使用中,这个模块作为我自己正在编写的一个xss_fuzz工具的一个部分而存在。
下面给出一些测试数据供大家参考(在普通网络状况):
Python爬虫开发解析(三-续):快速线程池爬虫
这个结果是在本机上测试的结果,在不同的电脑商测试结果均不同,8线程是比较小的线程数目,有兴趣的朋友可以采用16线程或者是更多的线程测试,效果可能更加明显,如果为了防止页面卡死,可以在worker中设置超时时间,一旦有那个页面一时间很难打开也能很快转换到新的页面,同样也能提高效率。
0×06 结语
关于爬虫的开发,我相信到现在,大家都已经没有什么问题了,如果要问网站爬行时候什么的页面权重怎么处理,简单无非是在爬虫过程中计算某个页面被多少页面所指(当然这个算法没有这么简单),并不是什么很高深的技术,如果有兴趣的小伙伴仍然可以去深入学习,大家都知道搜索引擎的核心也是爬虫技术。

 

 

www.dengb.comtruehttp://www.dengb.com/wzaq/1117797.htmlTechArticlePython爬虫开发解析(三-续):快速线程池爬虫 本文算是填前面的一个坑,有朋友和我将我前面写了这么多,真正没看到什么特别突出的实…

—-想了解更多的网站安全相关处理怎么解决关注<编程笔记>


推荐阅读
  • 利用Node.js实现PSD文件的高效切图
    本文介绍了如何通过Node.js及其psd2json模块,快速实现PSD文件的自动化切图过程,以适应项目中频繁的界面更新需求。此方法不仅提高了工作效率,还简化了从设计稿到实际应用的转换流程。 ... [详细]
  • H5技术实现经典游戏《贪吃蛇》
    本文将分享一个使用HTML5技术实现的经典小游戏——《贪吃蛇》。通过H5技术,我们将探讨如何构建这款游戏的两种主要玩法:积分闯关和无尽模式。 ... [详细]
  • HTML前端开发:UINavigationController与页面间数据传递详解
    本文详细介绍了如何在HTML前端开发中利用UINavigationController进行页面管理和数据传递,适合初学者和有一定基础的开发者学习。 ... [详细]
  • Java中的引用类型详解
    本文详细介绍了Java中的引用类型,包括强引用、软引用、弱引用和虚引用的特点和应用场景。 ... [详细]
  • Java高级工程师学习路径及面试准备指南
    本文基于一位朋友的PDF面试经验整理,涵盖了Java高级工程师所需掌握的核心知识点,包括数据结构与算法、计算机网络、数据库、操作系统等多个方面,并提供了详细的参考资料和学习建议。 ... [详细]
  • Docker基础入门与环境配置指南
    本文介绍了Docker——一款用Go语言编写的开源应用程序容器引擎。通过Docker,用户能够将应用及其依赖打包进容器内,实现高效、轻量级的虚拟化。容器之间采用沙箱机制,确保彼此隔离且资源消耗低。 ... [详细]
  • 本文详细介绍了如何使用C#实现不同类型的系统服务账户(如Windows服务、计划任务和IIS应用池)的密码重置方法。 ... [详细]
  • Spring Security基础配置详解
    本文详细介绍了Spring Security的基础配置方法,包括如何搭建Maven多模块工程以及具体的安全配置步骤,帮助开发者更好地理解和应用这一强大的安全框架。 ... [详细]
  • 本文提供了处理WordPress网站中出现过多重定向问题的方法,包括检查DNS配置、安装SSL证书以及解决数据库连接错误等步骤。 ... [详细]
  • 本文回顾了作者在求职阿里和腾讯实习生过程中,从最初的迷茫到最后成功获得Offer的心路历程。文中不仅分享了个人的面试经历,还提供了宝贵的面试准备建议和技巧。 ... [详细]
  • Python3爬虫入门:pyspider的基本使用[python爬虫入门]
    Python学习网有大量免费的Python入门教程,欢迎大家来学习。本文主要通过爬取去哪儿网的旅游攻略来给大家介绍pyspid ... [详细]
  • 尽管在WPF中工作了一段时间,但在菜单控件的样式设置上遇到了一些基础问题,特别是关于如何正确配置前景色和背景色。 ... [详细]
  • 解决PHP项目在服务器无法抓取远程网页内容的问题
    本文探讨了在使用PHP进行后端开发时,遇到的一个常见问题:即在本地环境中能够正常通过CURL获取远程网页内容,但在服务器上却无法实现。我们将分析可能的原因并提供解决方案。 ... [详细]
  • 从CodeIgniter中提取图像处理组件
    本指南旨在帮助开发者在未使用CodeIgniter框架的情况下,如何独立使用其强大的图像处理功能,包括图像尺寸调整、创建缩略图、裁剪、旋转及添加水印等。 ... [详细]
  • Bootstrap Paginator 分页插件详解与应用
    本文深入探讨了Bootstrap Paginator这款流行的JavaScript分页插件,提供了详细的使用指南和示例代码,旨在帮助开发者更好地理解和利用该工具进行高效的数据展示。 ... [详细]
author-avatar
mobiledu2502871567
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有