Python-定向爬虫的简单使用

  • 前言

初次接触Python,是以为测试同事用来做自动化测试,这两天有空“研究”了一下Python网络爬虫,所谓“研究”,其实就是了解,并跟着慕课网上的教学视频,写了一个爬取百度百科的定向爬虫。Demo传送门
注意:小生是刚接触Python,这里只是粗略记录下我的学习,所以深度优先,文中如有错误的地方请在留言处批评指正,小生感激不尽。

简单说,爬虫可以理解为网页蜘蛛,指通过我们特定的规则,自动化的从互联网获取我们需要的数据的一种技术手段。百度百科中有些简单的介绍,感兴趣的同学可以预先浏览学习下。

  • 原理

简单的网络爬虫,就是从一个网页的URL开始,下载该URL对应的网页,通过正则或其他技术手段从下载的内容中获取想要的数据进行存储或输出,并提取该URL中包含的其他URL,放入URL管理队列中,重复执行上述操作。

一般来说,爬虫程序的简单组成基本上可分为:URL管理器、数据下载器、数据解析器、应用程序等几部分。

  1. URL管理器:管理URL,包括已经爬取过数据的URL和未爬取即将爬取的URL。
  2. 数据下载器:通过一个URL下载网页,将网页转换成一个字符串,网页下载器有urllib2(Python官方基础模块)包括需要登录、代理、和cookie,requests(第三方包)。
  3. 数据解析器:将数据下载器下来的数据,按照我们的规则,进行数据提取。也可以根据DOM树的解析方式来解析。网页解析器有正则表达式(直观,将网页转成字符串通过模糊匹配的方式来提取有价值的信息,当文档比较复杂的时候,该方法提取数据的时候就会非常的困难)、html.parser(Python自带的)、beautifulsoup(第三方插件,可以使用Python自带的html.parser进行解析,也可以使用lxml进行解析,相对于其他几种来说要强大一些)、lxml(第三方插件,可以解析 xml 和 HTML),html.parser 和 beautifulsoup 以及 lxml 都是以 DOM 树的方式进行解析的。
  4. 应用程序:将数据解析器中提取的数据进行存储或输出。


    援引网络上一张爬虫工作流程的图片.png
  • 简单实践

大致了解了下爬虫的组成,我们就可以进行实践了,这里我们用的是Python语言,项目也很简单,只有几个文件,具体目录如下


目录.png
  1. URL管理器(url_manager.py):

    • class UrlManager(object):

      • init(self):初始化操作
      def __init__(self):
        # 使用set存储URL,防止重复
        self.new_urls = set()
        self.old_urls = set()
      
      • add_new_url(self, url):添加单个URL
      # 添加单个URL
      def add_new_url(self, url):
        # 检查是否为空
        if url is None:
          return
        # 检查是否已经添加到待爬取和未爬取集合中,若在return
        if url in self.new_urls and url in self.old_urls:
          return
        else:
          self.new_urls.add(url)
      
      • add_new_urls(self, urls):添加多个url
      # 添加多个url
      def add_new_urls(self, urls):
        if urls is None:
          return
        for url in urls:
          self.add_new_url(url)
      
      • has_new_url(self):是否还有未爬取数据的URL
      # 是否还有未爬取数据的URL
      def has_new_url(self):
        return len(self.new_urls) != 0
      
      • get_new_url(self):获取一个待爬取数据的URL
      # 获取一个待爬取数据的URL
      def get_new_url(self):
        # 在未爬去数据的URL中获取将要爬去数据的URL,并在new_urls中删除
        new_url = self.new_urls.pop()
        # 将new_url添加到已经使用的URL中
        self.old_urls.add(new_url)
        # 返回将要爬去数据的URL
        return new_url
      
  2. 数据下载器(html_download.py):

    • class HtmlDownloader(object):

      • 设置https自动校验
      # 如果访问的是https,会自动校验,此句停止自动校验
      ssl._create_default_https_context = ssl._create_unverified_context
      
      • download(self):下载网页
      @staticmethod
      def download(url):
        # 下载URL中的数据
        if url is None:
            return
      
        with request.urlopen(url) as f:
            if f.getcode() != 200:
                print('请求失败')
                return None
            return f.read().decode('utf-8')
      
  3. 数据解析器(html_parser.py):

    • class HtmlParser(object):

      • _get_new_urls(soup, page_url):从结果中提取符合规则的待爬取URL
      @staticmethod
      def _get_new_urls(soup, page_url):
        # /item/Python/407313,网页URL相对路径
        # new_urls 用于存放提取出来的新URL的集合,集合内不会存在相同元素
        new_urls = set()
        # links 是通过BeatifulSoup find_all按照一定的规则获取到的所有link,find_all方法查询所有符合条件的
        links = soup.find_all('a', href=re.compile(r'/item/\w+/\d+'))
        for link in links:
            new_url = link['href']
            # 拼接全路径
            new_full_url = urljoin(page_url, new_url)
            # 向集合中添加拼接后的全路径
            new_urls.add(new_full_url)
            print('page_url: %s, link: %s, new_url: %s, full_url: %s' % (page_url, link, new_url, new_full_url))
        return new_urls
      
      • _get_new_data(soup, page_url):从结果中提取符合规则的数据
      @staticmethod
      def _get_new_data(soup, page_url):
      
        res_data = {'url': page_url}
      
        # <dd class="lemmaWgt-lemmaTitle-title"><h1>Python</h1>
        # title_node = <h1>Python</h1> 
        # 获取标题节点
        title_node = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1')
        res_data['title'] = title_node.get_text()
      
        # <div class="lemma-summary" label-module="lemmaSummary">
        # summary  获取简介节点
        summary_node = soup.find('div', class_='lemma-summary')
        res_data['summary'] = summary_node.get_text()
        return res_data
      
      • parse(self, page_url, html_cont)
      def parse(self, page_url, html_cont):
        if page_url is None or html_cont is None:
            return
        soup = BeautifulSoup(html_cont, 'html.parser')
        # 提取网页中的其他URL
        new_urls = self._get_new_urls(soup, page_url)
        new_data = self._get_new_data(soup, page_url)
        return new_urls, new_data
      
  4. 输出/应用程序(html_output.py):

    • class HtmlOutputer(object):

      • init(self):
      def __init__(self):
        #初始化数组
        self.data = []
      
      • collect_data(self, data):将每一条解析出来的结果,存放到数组中,以便在HTML页面中输出
      # 将每一条解析出来的结果,存放到数组中,以便在HTML页面中输出
      def collect_data(self, data):
        self.data.append(data)
      
      • output_html(self):将结果遍历输出到网页resource.html中
      def output_html(self):
        with open('resource.html', 'w') as file:
          file.write('<html>')
          file.write('<head>')
          file.write('<meta charset = "utf-8">')
          file.write('<title>')
          file.write('爬取结果')
          file.write('</title>')
          file.write('</head>')
          file.write('<body>')
          file.write('<table border = "1" cellspacing = "0">')
          file.write('<tr><td>标题</td><td>链接</td><td>描述简介</td></tr>')
          for data in self.data:
              print(data)
              string = "<tr><td>%s</td><td><a href=%s>%s</a></td><td>%s</td></tr>" % (data['title'], data['url'], data['url'], data['summary'])
              file.write(string)
          file.write('</table>')
          file.write('</body>')
          file.write('</html>')
        file.close()
      
  5. 启动程序入口(spider_main.py)

    • class SpiderMain(object):
      • init(self):
      def __init__(self):
        self.urls = url_mananger.UrlManager()
        self.downloader = html_download.HtmlDownloader()
        self.parser = html_parser.HtmlParser()
        self.outputer = html_output.HtmlOutputer()
      
      • craw(self, root_url):
      def craw(self, root_url):
        if root_url is None:
          return
      
        count = 1
        # 向URL管理器中添加root_url
        self.urls.add_new_url(root_url)
        while self.urls.has_new_url():
          try:
              # 获取要爬去数据的URL
              new_url = self.urls.get_new_url()
              # 下载URL对应的html
              html_cont = self.downloader.download(new_url)
              # 解析下载的数据
              new_urls, new_data = self.parser.parse(new_url, html_cont)
              # 将解析出来的相关urls添加到urls.new_urls中
              self.urls.add_new_urls(new_urls)
              # 将解析出来的数据保存到outputer中
              self.outputer.collect_data(new_data)
      
              # 只爬取100条数据
              if count == 100:
                  break
              count = count + 1
          except ValueError:
              print('aaa,failed')
      
        # 输出一个html页面
        self.outputer.output_html()
      
      • main(): 启动初始化
      def main():
        # 设置入口URL
        root_url = 'https://baike.baidu.com/item/Python/407313'
        # 实例化一个爬虫对象
        obj_spider = SpiderMain()
        obj_spider.craw(root_url)
      
      • 启动程序
      if __name__ == '__main__':
        # 启动
        main()
      
  • Q&A

    Q1: https网络请求可能会验证不通过
    A1:在html_download.pyimport ssl,并加入如下代码

    ssl._create_default_https_context = ssl._create_unverified_context
    

    Q2:urllib2无法导入
    A2:可以使用from urllib import request,貌似3.x版本已经废弃了urllib2

    Q3:urlpath无法导入
    A3:可以使用from urllib.parse import urljoin

    Q4: 获取不到数据
    A4: 可能是百度百科换了URL格式,或者换了网页中元素标签,可查看网页源码,进行微调。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容