以视频爬取实例讲解Python爬虫神器Beautiful Soup用法

1.安装BeautifulSoup4
easy_install安装方式,easy_install需要提前安装

easy_install beautifulsoup4

pip安装方式,pip也需要提前安装.此外PyPi中还有一个名字是 BeautifulSoup 的包,那是 Beautiful Soup3 的发布版本.在这里不建议安装.

pip install beautifulsoup4

Debain或ubuntu安装方式

apt-get install Python-bs4

你也可以通过源码安装,下载BS4源码

Python setup.py install

2.小试牛刀

# coding=utf-8
'''
@通过BeautifulSoup下载百度贴吧图片
'''
import urllib
from bs4 import BeautifulSoup
url = 'http://tieba.baidu.com/p/3537654215'

# 下载网页
html = urllib.urlopen(url)
content = html.read()
html.close()

# 使用BeautifulSoup匹配图片
html_soup = BeautifulSoup(content)
# 图片代码我们在[Python爬虫基础1--urllib]( http://blog.xiaolud.com/2015/01/22/spider-1st/ "Python爬虫基础1--urllib")里面已经分析过了
# 相较通过正则表达式去匹配,BeautifulSoup提供了一个更简单灵活的方式
all_img_links = html_soup.findAll('img', class_='BDE_Image')

# 接下来就是老生常谈的下载图片
img_counter = 1
for img_link in all_img_links:
  img_name = '%s.jpg' % img_counter
  urllib.urlretrieve(img_link['src'], img_name)
  img_counter += 1

很简单,代码注释里面已经解释的很清楚了.BeautifulSoup提供了一个更简单灵活的方式,去分析网站源码,更快获取图片link.

3.爬取实例
3.1基本的抓取技术
在写一个爬虫脚本时,第一件事情就是手动观察要抓取的页面来确定数据如何定位。

首先,我们要看一看在 http://pyvideo.org/category/50/pycon-us-2014 上的 PyCon 大会视频列表。检查这个页面的 HTML 源代码我们发现视频列表的结果差不多是长这样的:

<div id="video-summary-content">
  <div class="video-summary">  <!-- first video -->
    <div class="thumbnail-data">...</div>
    <div class="video-summary-data">
      <div>
        <strong><a href="#link to video page#">#title#</a></strong>
      </div>
    </div>
  </div>
  <div class="video-summary">  <!-- second video -->
    ...
  </div>
  ...
</div>

那么第一个任务就是加载这个页面,然后抽取每个单独页面的链接,因为到 YouTube 视频的链接都在这些单独页面上。

使用requests来加载一个 web 页面是非常简单的:

import requests
response = requests.get('http://pyvideo.org/category/50/pycon-us-2014')

就是它!在这个函数返回后就能从response.text中获得这个页面的 HTML 。

下一个任务是抽取每一个单独视频页面的链接。通过 BeautifulSoup 使用 CSS 选择器语法就能完成它,如果你是客户端开发者的话你可能对这会很熟悉。

为了获得这些链接,我们要使用一个选择器,它能抓取在每一个 id 为video-summary-data的<div>中所有的<a>元素。由于每个视频都有几个<a>元素,我们将只保留那些 URL 以/video开头的<a>元素,这些就是唯一的单独视频页面。实现上述标准的 CSS 选择器是div.video-summary-data a[href^=/video]。下面的代码片段通过 BeautifulSoup 使用这个选择器来获得指向视频页面的<a>元素:

import bs4
soup = bs4.BeautifulSoup(response.text)
links = soup.select('div.video-summary-data a[href^=/video]')

因为我们真正关心的是这个链接本身而不是包含它的<a>元素,我们可以使用列表解析来改善上述代码。

links = [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]
现在,我们已经有了一个包含所有链接的数组,这些链接指向了每个单独页面。

下面这段脚本整理了目前我们提到的所有技术:

import requests
import bs4

root_url = 'http://pyvideo.org'
index_url = root_url + '/category/50/pycon-us-2014'

def get_video_page_urls():
  response = requests.get(index_url)
  soup = bs4.BeautifulSoup(response.text)
  return [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]

print(get_video_page_urls())

如果你运行上面这段脚本你将会获得一个满是 URL 的数组。现在我们需要去解析每个 URL 以获得更多关于每场 PyCon 会议的信息。

3.2抓取相连页面
下一步是加载我们的 URL 数组中每一个页面。如果你想要看看这些页面长什么样的话,这儿是个样例:http://pyvideo.org/video/2668/writing-restful-web-services-with-flask。没错,那就是我,那是我会议中的一个!

从这些页面我们可以抓取到会议的标题,在页面的顶部能看到它。我们也可以从侧边栏获得演讲者的姓名和 YouTube 的链接,侧边栏在嵌入视频的右下方。获取这些元素的代码展示在下方:

def get_video_data(video_page_url):
  video_data = {}
  response = requests.get(root_url + video_page_url)
  soup = bs4.BeautifulSoup(response.text)
  video_data['title'] = soup.select('div#videobox h3')[0].get_text()
  video_data['speakers'] = [a.get_text() for a in soup.select('div#sidebar a[href^=/speaker]')]
  video_data['youtube_url'] = soup.select('div#sidebar a[href^=http://www.youtube.com]')[0].get_text()

关于这个函数需要注意的一些事情:

从首页抓取的 URL 是相对路径,所以root_url需要加到前面。
大会标题是从 id 为videobox的<div>里的<h3>元素中获得的。注意[0]是必须的,因为调用select()返回的是一个数组,即使只有一个匹配。
演讲者的姓名和 YouTube 链接的获取方式与首页上的链接获取方式类似。
现在就剩下从每个视频的 YouTube 页面抓取观看数了。接着上面的函数写下去其实是非常简单的。同样,我们也可以抓取 like 数和 dislike 数。

def get_video_data(video_page_url):
  # ...
  response = requests.get(video_data['youtube_url'])
  soup = bs4.BeautifulSoup(response.text)
  video_data['views'] = int(re.sub('[^0-9]', '',
                   soup.select('.watch-view-count')[0].get_text().split()[0]))
  video_data['likes'] = int(re.sub('[^0-9]', '',
                   soup.select('.likes-count')[0].get_text().split()[0]))
  video_data['dislikes'] = int(re.sub('[^0-9]', '',
                    soup.select('.dislikes-count')[0].get_text().split()[0]))
  return video_data

上述调用soup.select()函数,使用指定了 id 名字的选择器,采集到了视频的统计数据。但是元素的文本需要被处理一下才能变成数字。考虑观看数的例子,在 YouTube 上显示的是"1,344 views"。用一个空格分开(split)数字和文本后,只有第一部分是有用的。由于数字里有逗号,可以用正则表达式过滤掉任何不是数字的字符。

为了完成爬虫,下面的函数调用了之前提到的所有代码:

def show_video_stats():
  video_page_urls = get_video_page_urls()
  for video_page_url in video_page_urls:
    print get_video_data(video_page_url)

3.3并行处理
上面到目前为止的脚本工作地很好,但是有一百多个视频它就要跑个一会儿了。事实上我们没做什么工作,大部分时间都浪费在了下载页面上,在这段时间脚本时被阻塞的。如果脚本能同时跑多个下载任务,可能就会更高效了,是吗?

回顾当时写一篇使用 Node.js 的爬虫文章的时候,并发性是伴随 JavaScript 的异步特性自带来的。使用 Python 也能做到,不过需要显示地指定一下。像这个例子,我将开启一个拥有8个可并行化进程的进程池。代码出人意料的简洁:

from multiprocessing import Pool

def show_video_stats(options):
  pool = Pool(8)
  video_page_urls = get_video_page_urls()
  results = pool.map(get_video_data, video_page_urls)

multiprocessing.Pool 类开启了8个工作进程等待分配任务运行。为什么是8个?这是我电脑上核数的两倍。当时实验不同大小的进程池时,我发现这是最佳的大小。小于8个使脚本跑的太慢,多于8个也不会让它更快。

调用pool.map()类似于调用常规的map(),它将会对第二个参数指定的迭代变量中的每个元素调用一次第一个参数指定的函数。最大的不同是,它将发送这些给进程池所拥有的进程运行,所以在这个例子中八个任务将会并行运行。

节省下来的时间是相当大的。在我的电脑上,第一个版本的脚本用了75秒完成,然而进程池的版本做了同样的工作只用了16秒!

3.4完成爬虫脚本
我最终版本的爬虫脚本在获得数据后还做了更多的事情。

我添加了一个--sort命令行参数去指定一个排序标准,可以指定views,likes或者dislikes。脚本将会根据指定属性对结果数组进行递减排序。另一个参数,--max代表了要显示的结果数的个数,万一你只想看排名靠前的几条而已。最后,我还添加了一个--csv选项,为了可以轻松地将数据导到电子制表软件中,可以指定数据以 CSV 格式打印出来,而不是表对齐格式。

完整脚本显示在下方:

import argparse
import re
from multiprocessing import Pool
import requests
import bs4

root_url = 'http://pyvideo.org'
index_url = root_url + '/category/50/pycon-us-2014'

def get_video_page_urls():
  response = requests.get(index_url)
  soup = bs4.BeautifulSoup(response.text)
  return [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]

def get_video_data(video_page_url):
  video_data = {}
  response = requests.get(root_url + video_page_url)
  soup = bs4.BeautifulSoup(response.text)
  video_data['title'] = soup.select('div#videobox h3')[0].get_text()
  video_data['speakers'] = [a.get_text() for a in soup.select('div#sidebar a[href^=/speaker]')]
  video_data['youtube_url'] = soup.select('div#sidebar a[href^=http://www.youtube.com]')[0].get_text()
  response = requests.get(video_data['youtube_url'])
  soup = bs4.BeautifulSoup(response.text)
  video_data['views'] = int(re.sub('[^0-9]', '',
                   soup.select('.watch-view-count')[0].get_text().split()[0]))
  video_data['likes'] = int(re.sub('[^0-9]', '',
                   soup.select('.likes-count')[0].get_text().split()[0]))
  video_data['dislikes'] = int(re.sub('[^0-9]', '',
                    soup.select('.dislikes-count')[0].get_text().split()[0]))
  return video_data

def parse_args():
  parser = argparse.ArgumentParser(description='Show PyCon 2014 video statistics.')
  parser.add_argument('--sort', metavar='FIELD', choices=['views', 'likes', 'dislikes'],
            default='views',
            help='sort by the specified field. Options are views, likes and dislikes.')
  parser.add_argument('--max', metavar='MAX', type=int, help='show the top MAX entries only.')
  parser.add_argument('--csv', action='store_true', default=False,
            help='output the data in CSV format.')
  parser.add_argument('--workers', type=int, default=8,
            help='number of workers to use, 8 by default.')
  return parser.parse_args()

def show_video_stats(options):
  pool = Pool(options.workers)
  video_page_urls = get_video_page_urls()
  results = sorted(pool.map(get_video_data, video_page_urls), key=lambda video: video[options.sort],
           reverse=True)
  max = options.max
  if max is None or max > len(results):
    max = len(results)
  if options.csv:
    print(u'"title","speakers", "views","likes","dislikes"')
  else:
    print(u'Views +1 -1 Title (Speakers)')
  for i in range(max):
    if options.csv:
      print(u'"{0}","{1}",{2},{3},{4}'.format(
        results[i]['title'], ', '.join(results[i]['speakers']), results[i]['views'],
        results[i]['likes'], results[i]['dislikes']))
    else:
      print(u'{0:5d} {1:3d} {2:3d} {3} ({4})'.format(
        results[i]['views'], results[i]['likes'], results[i]['dislikes'], results[i]['title'],
        ', '.join(results[i]['speakers'])))

if __name__ == '__main__':
  show_video_stats(parse_args())

下方输出的是在我写完代码时前25个观看数最多的会议:

(venv) $ python pycon-scraper.py --sort views --max 25 --workers 8
Views +1 -1 Title (Speakers)
 3002 27  0 Keynote - Guido Van Rossum (Guido Van Rossum)
 2564 21  0 Computer science fundamentals for self-taught programmers (Justin Abrahms)
 2369 17  0 Ansible - Python-Powered Radically Simple IT Automation (Michael Dehaan)
 2165 27  6 Analyzing Rap Lyrics with Python (Julie Lavoie)
 2158 24  3 Exploring Machine Learning with Scikit-learn (Jake Vanderplas, Olivier Grisel)
 2065 13  0 Fast Python, Slow Python (Alex Gaynor)
 2024 24  0 Getting Started with Django, a crash course (Kenneth Love)
 1986 47  0 It's Dangerous to Go Alone: Battling the Invisible Monsters in Tech (Julie Pagano)
 1843 24  0 Discovering Python (David Beazley)
 1672 22  0 All Your Ducks In A Row: Data Structures in the Standard Library and Beyond (Brandon Rhodes)
 1558 17  1 Keynote - Fernando Pérez (Fernando Pérez)
 1449  6  0 Descriptors and Metaclasses - Understanding and Using Python's More Advanced Features (Mike Müller)
 1402 12  0 Flask by Example (Miguel Grinberg)
 1342  6  0 Python Epiphanies (Stuart Williams)
 1219  5  0 0 to 00111100 with web2py (G. Clifford Williams)
 1169 18  0 Cheap Helicopters In My Living Room (Ned Jackson Lovely)
 1146 11  0 IPython in depth: high productivity interactive and parallel python (Fernando Perez)
 1127  5  0 2D/3D graphics with Python on mobile platforms (Niko Skrypnik)
 1081  8  0 Generators: The Final Frontier (David Beazley)
 1067 12  0 Designing Poetic APIs (Erik Rose)
 1064  6  0 Keynote - John Perry Barlow (John Perry Barlow)
 1029 10  0 What Is Async, How Does It Work, And When Should I Use It? (A. Jesse Jiryu Davis)
 981 11  0 The Sorry State of SSL (Hynek Schlawack)
 961 12  2 Farewell and Welcome Home: Python in Two Genders (Naomi Ceder)
 958  6  0 Getting Started Testing (Ned Batchelder)
(0)

相关推荐

  • Python实现爬取知乎神回复简单爬虫代码分享

    看知乎的时候发现了一个 "如何正确地吐槽" 收藏夹,里面的一些神回复实在很搞笑,但是一页一页地看又有点麻烦,而且每次都要打开网页,于是想如果全部爬下来到一个文件里面,是不是看起来很爽,并且随时可以看到全部的,于是就开始动手了. 工具 1.Python 2.7 2.BeautifulSoup 分析网页 我们先来看看知乎上该网页的情况 网址:,容易看到,网址是有规律的,page慢慢递增,这样就能够实现全部爬取了. 再来看一下我们要爬取的内容: 我们要爬取两个内容:问题和回答,回答仅限于显示

  • Python爬取Coursera课程资源的详细过程

    有时候我们需要把一些经典的东西收藏起来,时时回味,而Coursera上的一些课程无疑就是经典之作.Coursera中的大部分完结课程都提供了完整的配套教学资源,包括ppt,视频以及字幕等,离线下来后会非常便于学习.很明显,我们不会去一个文件一个文件的下载,只有傻子才那么干,程序员都是聪明人! 那我们聪明人准备怎么办呢?当然是写一个脚本来批量下载了.首先我们需要分析一下手工下载的流程:登录自己的Coursera账户(有的课程需要我们登录并选课后才能看到相应的资源),在课程资源页面里,找到相应的文件

  • python爬虫教程之爬取百度贴吧并下载的示例

    测试url:http://tieba.baidu.com/p/27141123322?pn=begin  1end   4 复制代码 代码如下: import string ,urllib2 def baidu_tieba(url,begin_page,end_page):    for i in range(begin_page, end_page+1):        sName = string.zfill(i,5)+ '.html'        print '正在下载第' + str(

  • python爬取网站数据保存使用的方法

    编码问题因为涉及到中文,所以必然地涉及到了编码的问题,这一次借这个机会算是彻底搞清楚了.问题要从文字的编码讲起.原本的英文编码只有0~255,刚好是8位1个字节.为了表示各种不同的语言,自然要进行扩充.中文的话有GB系列.可能还听说过Unicode和UTF-8,那么,它们之间是什么关系呢?Unicode是一种编码方案,又称万国码,可见其包含之广.但是具体存储到计算机上,并不用这种编码,可以说它起着一个中间人的作用.你可以再把Unicode编码(encode)为UTF-8,或者GB,再存储到计算机

  • 通过抓取淘宝评论为例讲解Python爬取ajax动态生成的数据(经典)

    在学习python的时候,一定会遇到网站内容是通过 ajax动态请求.异步刷新生成的json数据 的情况,并且通过python使用之前爬取静态网页内容的方式是不可以实现的,所以这篇文章将要讲述如果在python中爬取ajax动态生成的数据. 至于读取静态网页内容的方式,有兴趣的可以查看本文内容. 这里我们以爬取淘宝评论为例子讲解一下如何去做到的. 这里主要分为了四步: 一 获取淘宝评论时,ajax请求链接(url) 二 获取该ajax请求返回的json数据 三 使用python解析json数据

  • 利用Python爬取可用的代理IP

    前言 就以最近发现的一个免费代理IP网站为例:http://www.xicidaili.com/nn/.在使用的时候发现很多IP都用不了. 所以用Python写了个脚本,该脚本可以把能用的代理IP检测出来. 脚本如下: #encoding=utf8 import urllib2 from bs4 import BeautifulSoup import urllib import socket User_Agent = 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv

  • python动态网页批量爬取

    四六级成绩查询网站我所知道的有两个:学信网(http://www.chsi.com.cn/cet/)和99宿舍(http://cet.99sushe.com/),这两个网站采用的都是动态网页.我使用的是学信网,好了,网站截图如下: 网站的代码如下: <form method="get" name="form1" id="form1" action="/cet/query"> <table border=&qu

  • python 爬取微信文章

    本人想搞个采集微信文章的网站,无奈实在从微信本生无法找到入口链接,网上翻看了大量的资料,发现大家的做法总体来说大同小异,都是以搜狗为入口.下文是笔者整理的一份python爬取微信文章的代码,有兴趣的欢迎阅读 #coding:utf-8 author = 'haoning' **#!/usr/bin/env python import time import datetime import requests** import json import sys reload(sys) sys.setd

  • python实现爬取千万淘宝商品的方法

    本文实例讲述了python实现爬取千万淘宝商品的方法.分享给大家供大家参考.具体实现方法如下: import time import leveldb from urllib.parse import quote_plus import re import json import itertools import sys import requests from queue import Queue from threading import Thread URL_BASE = 'http://s

  • python爬取w3shcool的JQuery课程并且保存到本地

    最近在忙于找工作,闲暇之余,也找点爬虫项目练练手,写写代码,知道自己是个菜鸟,但是要多加练习,书山有路勤为径.各位爷有测试坑可以给我介绍个啊,自动化,功能,接口都可以做. 首先呢,我们明确需求,很多同学呢,有事没事就想看看一些技术,比如我想看看JQuery的语法呢,可是我现在没有网络,手机上也没有电子书,真的让我们很难受,那么别着急啊,你这需求我在这里满足你,首先呢,你的需求是获取JQuery的语法的,那么我在看到这个需求,我有响应的网站那么我们接下来去分析这个网站.http://www.w3s

随机推荐