python很全的爬虫入门教程

python很全的爬虫入门教程

一、爬虫前的准备工作
首先,我们要知道什么是爬虫
1、什么是网络爬虫?

网络爬虫是一种按照一定的规则,自动的抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。

简单的来说,网络爬虫就是一段程序,它模拟人类访问互联网的形式,不停地从网络上获取我们需求的数据。我们可以制定各种各样的爬虫,来满足不同的需求,如果法律允许,你可以采集在网页上看到的任何你想要获得的数据。

爬虫是一种从网络上高速提取数据的方式(当然他也可以用作它途,如果需要的话,因为从本质上来说,太就是利用python与网站进行交互、并对网站返回的结果进行分析和处理的过程)。你可以把爬虫想成一个机器人(其实他就是个机器人,不过是软件形式上的),坐在一台电脑旁边,不停地点开一个网页,从里面复制指定文本或图片进行保存(假设需求就是保存指定文本或图片)。神奇的是,他的手速非常的快,一阵眼花缭乱中,本地磁盘就已经存了一大堆的数据了。

2、网络爬虫能做什么?

上面说了一大堆,可能也没说清楚爬虫究竟是什么。没关系,我们就举几个例子来看哦不想。

比如,学校经常在官网上发布一些比较重要的通知,我不想每天都花费精力去看官网,却又想当有新通知的时候,就能知道,并看到它。

这种时候,就需要爬虫来帮忙咯。写一个程序,让它每半个小时或一个小时就去访问一次官网,检查有没有新的通知,如果没有,就什么都不做,等待下次检查,如果有,就将新通知从网页中提取出来,保存,并发邮件告诉我们通知的内容,然后继续等待即可。

假设,最近有点闲了,想看看电影,但又不想看烂片。于是,默默打开了豆瓣,上面有电影评分嘛,还有影评。我想要获取所以评分在8分以上的电影名称、简介以及该电影的部分热评,从中选出想看的出来。

这个时候,一个小小的爬虫就能轻轻松松地从一堆电影中找出符合要求的保存下来,不用费神地一个个去瞅了。如果你还会自然语言处理和机器学习,那就更棒了,或许你可以直接对这些数据进行分析,让程序匹配出你感兴趣的电影来。(当然了,举例子嘛,现实生活中,显然投入和产出不成正比= =看个电影哪那么麻烦orz)

再比如,采集京东、淘宝的商品评论信息啦,采集招聘网站的企业职位信息啦,采集微博信息啦,或者只是简单地爬一些美女图片啦……各种情况,采什么,看需求吧。

3、开发爬虫的准备工作

3.1编程语言

爬虫之前,我们需要了解python的基础语法。更深入的用法可以不用太着急去学,在使用的过程中碰到各种问题,再去学习并解决就可以了。关于python的用法,我就不写了,网上有很多教程。这里推荐一下廖雪峰老师的python教程,分python2.7和python3.5两个版本,我开发使用的是2.7。

3.2开发平台和环境

开发平台推荐使用linux。

pycharm 远程调试

3.3 学习爬虫的必备知识

大家要先对以下内容有一定的了解再来学习爬虫哦,磨刀不误砍柴工

  • HTML
    这个能够帮助你了解网页的结构,内容等。可以参考W3School的教程。
  • Python
    如果有编程基础的小伙伴儿,推荐看一个廖雪峰的Python教程就够了
    没有编程基础的小伙伴,推荐看看视频教程(网易云课堂搜Python),然后再结合廖雪峰的教程,双管齐下。
    其实知乎上总结的已经非常好了,我就不多唠叨了。知乎-如何系统的自学Python
  • TCP/IP协议,HTTP协议
    这些知识能够让你了解在网络请求和网络传输上的基本原理,了解就行,能够帮助今后写爬虫的时候理解爬虫的逻辑。
    廖雪峰Python教程里也有简单介绍,可以参考:TCP/IP简介,HTTP协议
    想更深入学习的小伙伴儿可以去网上多搜搜相关的书籍哦

二、开始一个简单的爬虫

Python版本

我电脑装了好多个Python版本(学一个装一个。。。),不过推荐使用Anaconda这个科学计算版本,主要是因为它自带一个包管理工具,可以解决有些包安装错误的问题。去Anaconda官网,选择Python3.5版本,然后下载安装。

1、爬虫的过程分析

当人去访问一个网页的时候,输入要访问的网址,发起请求。

等待服务器返回数据,通过浏览器加载网页。

从网页中找到自己需要的数据(文本、图片、文件等等)。

保存自己需要的数据。

对于爬虫,也是类似的。它模仿人类请求网页的过程,但是又稍有不同。

首先,对应于上面的①和②步骤,我们要利用python实现请求一个网页的功能。

其次,对应于上面的③步骤,我们要利用python实现解析请求到的网页的功能。

最后,对于上面的④步骤,我们要利用python实现保存数据的功能。

因为是讲一个简单的爬虫嘛,所以一些其他的复杂操作这里就不说了。下面,针对上面几个功能,逐一进行分析。

2、如何用python请求一个网页

作为一门拥有丰富类库的编程语言,利用python请求网页完全 不在话下。这里推荐一个非常好用的第三方类库requests。

2.1requests

2.1.1 安装方式

打开终端cmd,在里面输入以下指令并回车

pip install requests

如果使用Anaconda这个科学计算版本 输入以下指令:

conda install requests

参考:PyPI使用国内源

2.1.2测试是否安装成功

在命令行中输入python 敲击回车,进入python交互环境。在里面输入以下代码并回车:

import requests

如果不报错,就安装成功了

2.2使用requests请求网页

打开pycharm,创建一个项目。

创建成功后,在创建一个py文件,用来写代码。

import requests
resp=requests.get('https://www.baidu.com') #请求百度首页
print resp #打印请求结果的状态码
print resp.content #打印请求到的网页源码

对上面的代码进行以下简单的分析:

第1行:引入requests包。

第2行:使用requests类库,以get的方式请求网址https://www.baidu.com,并将服务器返回的结果封装成一个对象,用变量resp来接收它。

第3行:一般可以根据状态码来判断是否请求成功,正常的状态码是200,异常状态码就很多了,比如404(找不到网页)、301(重定向)等。

第4行:打印网页的源码。注意,只是源码。不像是浏览器,在获取到源码之后,还会进一步地取请求源码中引用的图片等信息,如果有JS,浏览器还会执行JS,对页面显示的内容进行修改。使用requests进行请求,我们能够直接获取到的,只有最初始的网页源码。也正是因为这样,不加载图片、不执行JS等等,爬虫请求的速度会非常快。

代码很短吧?一行就完成了请求,可以,这很python。 现在,运行一下代码看看吧。

2.3 requests库的使用

requests 库就是用来发送各种请求的,所以,我们就来看看各种请求怎么使用:

2.3.1 get 请求

r=requests.get("http://www.baidu.com")
# 这就是我们刚刚用到的。其实就是向网站发送了一个get请求,然后网站会返回一个response。r 就是response。大家可以在运行的时候查看r的type。
print(type(r)) #<class 'requests.models.Response'>

get请求还可以传递参数:

payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.get("http://httpbin.org/get", params=payload)

上面代码向服务器发送的请求中包含了两个参数key1和key2,以及两个参数的值。实际上它构造成了如下网址:

http://httpbin.org/get?key1=value1&key2=value2

2.3.2 post请求

无参数的post请求:

r = requests.post(“http://httpbin.org/post”)

有参数的post请求:

payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post("http://httpbin.org/post", data=payload)

post请求多用来提交表单数据,即填写一堆输入框,然后提交。

2.3.3 其他请求

其他一些请求例如put请求、delete请求、head请求、option请求等其实都是类似的。但是平时用的不多,就不仔细介绍了。有用到的可以去看官网文档哦。阅读官方文档是必备技能!

r = requests.put("http://httpbin.org/put")
r = requests.delete("http://httpbin.org/delete")
r = requests.head("http://httpbin.org/get")
r = requests.options("http://httpbin.org/get")

3、如何用python解析网页源码

3.1.1 BeautifulSoup

这是我比较推荐的一款解析器,简单易用,容易理解。 

但是使用bs4还需要安装另一个类库lxml,用来代替bs4默认的解析器。之所以这样做,是因为默认的那个实在太慢了,换用了lxml后,可以大幅度提升解析速度。

3.1.1.1 安装

命令行中输入以下指令并回车,安装bs4:

pip install beautifulsoup4

这是我比较推荐的一款解析器,简单易用,容易理解。 但是使用bs4还需要安装另一个类库lxml,用来代替bs4默认的解析器。之所以这样做,是因为默认的那个实在太慢了,换用了lxml后,可以大幅度提升解析速度。

3.1.1.2 测试是否安装成功

进入python交互环境,引用bs4和lxml类库,不报错即安装成功。

import bs4

import lxml

3.1.1.3文档解析对照表

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup,“html.parser”) 1. Python的内置标准库2. 执行速度适3. 中文档容错能力强 Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup,“lxml”) 1. 速度快2. 文档容错能力强 需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup,[“lxml-xml”]) BeautifulSoup(markup,“xml”) 1. 速度快2. 唯一支持XML的解析器 需要安装C语言库
html5lib BeautifulSoup(markup,“html5lib”) 1. 最好的容错性2. 以浏览器的方式解析文档3. 生成HTML5格式的文档 速度慢,不依赖外部扩展

3.1.1.4 BeautifulSoup对象的类型

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象。所有对象可以归纳为4种类型: Tag , NavigableString , BeautifulSoup , Comment 。下面我们分别看看这四种类型都是什么东西。

1.Tag

这个就跟HTML或者XML(还能解析XML?是的,能!)中的标签是一样一样的。我们使用find()方法返回的类型就是这个(插一句:使用find-all()返回的是多个该对象的集合,是可以用for循环遍历的。)。返回标签之后,还可以对提取标签中的信息。

提取标签的名字:

tag.name

提取标签的属性:

tag[‘attribute’]

我们用一个例子来了解这个类型:

from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
soup = BeautifulSoup(html_doc, 'lxml')  #声明BeautifulSoup对象
find = soup.find('p')  #使用find方法查到第一个p标签
print("find's return type is ", type(find))  #输出返回值类型
print("find's content is", find)  #输出find获取的值
print("find's Tag Name is ", find.name)  #输出标签的名字
print("find's Attribute(class) is ", find['class'])  #输出标签的class属性值

2、 NavigableString

NavigableString就是标签中的文本内容(不包含标签)。获取方式如下:

tag.string

还是以上面那个例子,加上下面这行,然后执行:

print(‘NavigableString is:’, find.string)

3、 BeautifulSoup

BeautifulSoup对象表示一个文档的全部内容。支持遍历文档树和搜索文档树。

4、Comment

这个对象其实就是HTML和XML中的注释。

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>

有些时候,我们并不想获取HTML中的注释内容,所以用这个类型来判断是否是注释。

if type(SomeString) == bs4.element.Comment:
    print('该字符是注释')
else:
    print('该字符不是注释')

3.1.1.5 BeautifulSoup遍历方法

1.节点和标签名

可以使用子节点、父节点、 及标签名的方式遍历:

soup.head #查找head标签
soup.p #查找第一个p标签
#对标签的直接子节点进行循环
for child in title_tag.children:
    print(child)
soup.parent #父节点
# 所有父节点
for parent in link.parents:
    if parent is None:
        print(parent)
    else:
        print(parent.name)
# 兄弟节点
sibling_soup.b.next_sibling #后面的兄弟节点
sibling_soup.c.previous_sibling #前面的兄弟节点
#所有兄弟节点
for sibling in soup.a.next_siblings:
    print(repr(sibling))
for sibling in soup.find(id="link3").previous_siblings:
    print(repr(sibling))

2、搜索文档树

最常用的当然是find()和find_all()啦,当然还有其他的。比如find_parent() 和 find_parents()、 find_next_sibling() 和 find_next_siblings() 、find_all_next() 和 find_next()、find_all_previous() 和 find_previous() 等等。

我们就看几个常用的,其余的如果用到就去看官方文档哦。

  • find_all()
    搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。返回值类型是bs4.element.ResultSet。
    完整的语法:
    find_all( name , attrs , recursive , string , **kwargs )
    这里有几个例子:
    soup.find_all(“title”)# [The Dormouse’s story]
    soup.find_all(“p”, “title”)# [

    The Dormouse’s story

    ]
    soup.find_all(“a”)# [Elsie,
    # Lacie,
    # Tillie]
    #
    soup.find_all(id=“link2”)
    # [Lacie]
    import re
    soup.find(string=re.compile(“sisters”))
    # u’Once upon a time there were three little sisters; and their names were\n’
    name 参数:可以查找所有名字为 name 的tag。
    attr 参数:就是tag里的属性。
    string 参数:搜索文档中字符串的内容。
    recursive 参数: 调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点。如果只想搜索tag的直接子节点,可以使用参数 recursive=False 。
    find()
    与find_all()类似,只不过只返回找到的第一个值。返回值类型是bs4.element.Tag。
    完整语法:
    find( name , attrs , recursive , string , **kwargs )
    看例子:
    soup.find(‘title’)
    The Dormouse’s story
    soup.find(“head”).find(“title”)
    The Dormouse’s story

3.1.2 正则

这个不用安装,标准库里带的就有。

正则的优点:①速度快 ②能够提取有些解析器提取不到的数据

正则的缺点:①不够直观,很难从面向对象的角度来考虑数据的提取 ②你得会写正则表达式 。

教程就不放了,善用百度嘛。正则一般用来满足特殊需求、以及提取其他解析器提取不到的数据,正常情况下我会用bs4,bs4无法满足就用正则。

3.1.3 pyquery

这个解析器的语法和jQuery很相似,所以写过jQuery的同学用起来可能比较容易上手。国内有个dalao写的爬虫框架pyspider用的就是这个解析器。

如果没用过jQuery,那就在bs4和pyquery两个里面选一个学吧,一般情况下会一个就够了。

3.1.3.1 安装

pip install pyquery

3.1.3.2 测试

import pyquery

3.2 使用BeautifulSoup+lxml解析网页源码

接着上面的代码来,我们使用BeautifulSoup+lxml解析请求到的网页源码。 

从百度的首页,可以通过点击跳转到很多其他页面,比如说下面圈起来的,点击都会跳转到新的页面:

现在,我们想要用python获得从百度能够跳转到的页面的链接,该怎么做? 

代码很简单,接着上面的写:

import requests
from bs4 import BeautifulSoup
resp=requests.get('https://www.baidu.com') #请求百度首页
print resp #打印请求结果的状态码
print resp.content #打印请求到的网页源码
bsobj=BeautifulSoup(resp.content,'lxml') #将网页源码构造成BeautifulSoup对象,方便操作
a_list=bsobj.find_all('a') #获取网页中的所有a标签对象
for a in a_list:
    print a.get('href') #打印a标签对象的href属性,即这个对象指向的链接地址

首先,第2行,引入我们解析时要使用的类库,beautifulsoup4。 

第6行,将网页的源码转化成了BeautifulSoup的对象,这样我们可以向操作DOM模型类似地去操作它。

第7行,从这个BeautifulSoup对象中,获取所有的a标签对象(大家应该知道a标签对象是什么吧,网页中的链接绝大多数都是a对象实现的),将他们组成一个列表,也就是a_list。

第8、9行,遍历这个列表,对于列表中的每一个a标签对象,获取它的属性href的值(href属性记录一个a标签指向的链接地址)。获取一个标签对象的属性,可以使用get(’xx’)方法,比如a_tag是一个a标签对象,获取它的href的值,就是a_tag.get(‘href’),获取它的class信息可以用a_tag.get(‘class’),这将返回一个修饰该标签的class列表。

运行一下,可以看到,打印出了很多链接。

这是个简单的例子,介绍如何开始一个简单爬虫,不涉及复杂操作(复杂的后面会上小项目,会介绍)。关于beautifulsoup的详细用法,请自行百度。

3.3 简单的保存数据的方法

保存数据的方法大概可以分为几类:保存文本、保存二进制文件(包括图片)、保存到数据库。保存二进制文件和保存到数据库后面会具体说,这里简单讲一下怎么保存到文本。

python里面操作文本相当的简单。现在,我将刚才提取出来的链接保存到一个名称为url.txt的文本里面去,将上面的代码稍作修改。

import requests
from bs4 import BeautifulSoup
resp=requests.get('https://www.baidu.com') #请求百度首页
print resp #打印请求结果的状态码
print resp.content #打印请求到的网页源码
bsobj=BeautifulSoup(resp.content,'lxml') #将网页源码构造成BeautifulSoup对象,方便操作
a_list=bsobj.find_all('a') #获取网页中的所有a标签对象
text='' # 创建一个空字符串
for a in a_list:
    href=a.get('href') #获取a标签对象的href属性,即这个对象指向的链接地址
    text+=href+'\n' #加入到字符串中,并换行
with open('url.txt','w') as f: #在当前路径下,以写的方式打开一个名为'url.txt',如果不存在则创建
    f.write(text) #将text里的数据写入到文本中

三、实例

通过仔细观察,发现图片的链接在style中的background-image中有个url。这个url就包含了图片的地址,url后面跟了一堆参数,可以看到其中有&w=XXX&h=XXX,这个是宽度和高度参数。我们把高度和宽度的参数去掉,就能获取到大图。下面,我们先获取到所有的含有图片的a标签,然后在循环获取a标签中的style内容。

其实在图片的右下方有一个下载按钮,按钮的标签中有一个下载链接,但是该链接并不能直接请求到图片,需要跳转几次,通过获取表头里的Location才能获取到真正的图片地址。后续我再以这个角度获取图片写一篇博文,咱们现根据能直接获取到的url稍做处理来获取图片。小伙伴儿们也可能会发现其他的方式来获取图片的url,都是可以的,尽情的尝试吧!

import requests #导入requests 模块
from bs4 import BeautifulSoup  #导入BeautifulSoup 模块
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36'}  #给请求指定一个请求头来模拟chrome浏览器
web_url = 'https://unsplash.com'r = requests.get(web_url, headers=headers) #像目标url地址发送get请求,返回一个response对象
all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d')  #获取网页中的class为cV68d的所有a标签
for a in all_a:
  print(a['style']) #循环获取a标签中的style

这里的find_all(‘a’, class_=‘cV68d’) 是找到所有class为cV68d的a标签,返回的是一个list,所以可以用for循环获取每个a标签。

还有,get请求使用了headers参数,这个是用来模拟浏览器的。如何知道‘User-Agent’是什么呢?在你的Chrome浏览器中,按F12,然后刷新网页,看下图就可以找到啦。

接下来的任务是在一行的文本中取到图片的url。仔细看每一行的字符串,两个双引号之间的内容就是图片的url了,所以我们Python的切片功能来截取这中间的内容。

改写for循环中的内容:

for a in all_a: 
    img_str = a['style'] #a标签中完整的style字符串
    print(img_str[img_str.index('"')+1 : img_str.index('"',img_str[img_str.index('"')+1)]) #使用Python的切片功能截取双引号之间的内容

获取到url后还要把宽度和高度的参数去掉。

 for a in all_a:
            img_str = a['style'] #a标签中完整的style字符串
            print('a标签的style内容是:', img_str)
            first_pos = img_str.index('"') + 1
            second_pos = img_str.index('"',first_pos)
            img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容
            width_pos = img_url.index('&w=')
            height_pos = img_url.index('&q=')
            width_height_str = img_url[width_pos : height_pos]
            print('高度和宽度数据字符串是:', width_height_str)
            img_url_final = img_url.replace(width_height_str, '')
            print('截取后的图片的url是:', img_url_final)

有了这些图片的url,就可以通过继续发请求的方式获取图片啦。接下来我们先来封装一下发请求的代码。

先创建一个类:

class BeautifulPicture(): 
   def __init__(self):  #类的初始化操作
        self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}  #给请求指定一个请求头来模拟chrome浏览器
        self.web_url = 'https://unsplash.com' #要访问的网页地址
        self.folder_path = 'D:\BeautifulPicture'  #设置图片要存放的文件目录

然后封装request请求:

def request(self, url):  #返回网页的response
        r = requests.get(url)  # 像目标url地址发送get请求,返回一个response对象
        return r

我们在文件目录下保存图片的话,要先创建文件目录。所以再添加一个创建目录的方法:

要先引入os库哦。import os

然后是方法定义:

    def mkdir(self, path):  ##这个函数创建文件夹
        path = path.strip()
        isExists = os.path.exists(path)
        if not isExists:
            print('创建名字叫做', path, '的文件夹')
            os.makedirs(path)
            print('创建成功!')
        else:
            print(path, '文件夹已经存在了,不再创建')

然后就是保存图片

    def save_img(self, url, name): ##保存图片
        print('开始保存图片...')
        img = self.request(url)
        time.sleep(5)
        file_name = name + '.jpg'
        print('开始保存文件')
        f = open(file_name, 'ab')
        f.write(img.content)
        print(file_name,'文件保存成功!')
        f.close()    def mkdir(self, path):  ##这个函数创建文件夹
        path = path.strip()
        isExists = os.path.exists(path)
        if not isExists:
            print('创建名字叫做', path, '的文件夹')
            os.makedirs(path)
            print('创建成功!')
        else:
            print(path, '文件夹已经存在了,不再创建')

工具方法都已经准备完毕,开始我们的逻辑部分:

    def get_pic(self):
        print('开始网页get请求')
        r = self.request(self.web_url)
        print('开始获取所有a标签')
        all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d')  #获取网页中的class为cV68d的所有a标签
        print('开始创建文件夹')
        self.mkdir(self.folder_path)  #创建文件夹
        print('开始切换文件夹')
        os.chdir(self.folder_path)   #切换路径至上面创建的文件夹
        i = 1 #后面用来给图片命名
        for a in all_a:
            img_str = a['style'] #a标签中完整的style字符串
            print('a标签的style内容是:', img_str)
            first_pos = img_str.index('"') + 1
            second_pos = img_str.index('"',first_pos)
            img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容
            width_pos = img_url.index('&w=')
            height_pos = img_url.index('&q=')
            width_height_str = img_url[width_pos : height_pos]
            print('高度和宽度数据字符串是:', width_height_str)
            img_url_final = img_url.replace(width_height_str, '')
            print('截取后的图片的url是:', img_url_final)
            self.save_img(img_url_final, str(i))
            i += 1

最后就是执行啦:

beauty = BeautifulPicture() #创建一个类的实例

beauty.get_pic() #执行类中的方法

最后来一个完整的代码

import requests #导入requests 模块
from bs4 import BeautifulSoup  #导入BeautifulSoup 模块
import os  #导入os模块

class BeautifulPicture():

    def __init__(self):  #类的初始化操作
        self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1'}  #给请求指定一个请求头来模拟chrome浏览器
        self.web_url = 'https://unsplash.com'  #要访问的网页地址
        self.folder_path = 'D:\BeautifulPicture'  #设置图片要存放的文件目录

    def get_pic(self):
        print('开始网页get请求')
        r = self.request(self.web_url)
        print('开始获取所有a标签')
        all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d')  #获取网页中的class为cV68d的所有a标签
        print('开始创建文件夹')
        self.mkdir(self.folder_path)  #创建文件夹
        print('开始切换文件夹')
        os.chdir(self.folder_path)   #切换路径至上面创建的文件夹

        for a in all_a: #循环每个标签,获取标签中图片的url并且进行网络请求,最后保存图片
            img_str = a['style'] #a标签中完整的style字符串
            print('a标签的style内容是:', img_str)
            first_pos = img_str.index('"') + 1
            second_pos = img_str.index('"',first_pos)
            img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容
            #获取高度和宽度的字符在字符串中的位置
            width_pos = img_url.index('&w=')
            height_pos = img_url.index('&q=')
            width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和宽度参数,后面用来将该参数替换掉
            print('高度和宽度数据字符串是:', width_height_str)
            img_url_final = img_url.replace(width_height_str, '')  #把高度和宽度的字符串替换成空字符
            print('截取后的图片的url是:', img_url_final)
            #截取url中参数前面、网址后面的字符串为图片名
            name_start_pos = img_url.index('photo')
            name_end_pos = img_url.index('?')
            img_name = img_url[name_start_pos : name_end_pos]
            self.save_img(img_url_final, img_name) #调用save_img方法来保存图片

    def save_img(self, url, name): ##保存图片
        print('开始请求图片地址,过程会有点长...')
        img = self.request(url)
        file_name = name + '.jpg'
        print('开始保存图片')
        f = open(file_name, 'ab')
        f.write(img.content)
        print(file_name,'图片保存成功!')
        f.close()

    def request(self, url):  #返回网页的response
        r = requests.get(url, headers=self.headers)  # 像目标url地址发送get请求,返回一个response对象。有没有headers参数都可以。
        return r

    def mkdir(self, path):  ##这个函数创建文件夹
        path = path.strip()
        isExists = os.path.exists(path)
        if not isExists:
            print('创建名字叫做', path, '的文件夹')
            os.makedirs(path)
            print('创建成功!')
        else:
            print(path, '文件夹已经存在了,不再创建')

beauty = BeautifulPicture()  #创建类的实例
beauty.get_pic()  #执行类中的方法

四、 PhatomJS + Selenium

1、工具的介绍

在爬取Unsplash网站的时候,由于网站是下拉刷新,并没有分页。所以不能够通过页码获取页面的url来分别发送网络请求。我也尝试了其他方式,比如下拉的时候监控http请求,看看请求是否有规律可以模拟。后来发现请求并没有规律,也就是不能够模拟http请求来获取新的数据(也可能是我水平有限,哪位童鞋找到了规律一定要告诉我哦)。那么就只有模拟下拉操作了。

想要模拟下拉操作,我们需要用到两个工具,一个是PhatomJs,一个是Selenium。

PhatomJS其实就是一个没有界面的浏览器,最主要的功能是能够读取js加载的页面。

Selenium实质上是一个自动化测试工具,能够模拟用户的一些行为操作,比如下拉网页。

一直有个说法,Python + PhatomJS + Selenium 是爬虫的无敌三件套,基本能够实现所有爬虫需求。

2、PhatomJS

2.1简介

hatomJS是一个WebKit内核的浏览器引擎,它能像浏览器一样(它就是一个浏览器,只不过没有界面)解析网页,以及运行JavaScript脚本。

迄今为止,我们的实战小爬虫只是在模拟http请求,然后获取response,从response中解析HTML代码获得想要的数据。但是,网页中有些数据是用js动态加载的,这样,我们使用使用http请求获得的数据中并不包含js动态加载的内容。比如我在本系列的第一篇博文中请求了一下网易云音乐的网站,本来想用网易云音乐做实战示例的(我的设计师小伙伴儿的另一个需求),不过由于它是由js动态加载的,用不到requests库,所以就先以Unsplash网站为实例了。写完Unsplash网站的爬虫后就开始网易云音乐的实战!

那么,怎么判断一个网站的内容是不是js动态加载的呢?

打开网易云音乐,在页面上点击右键–>View page source。会弹出一个新窗口,在新窗口中搜索你想要爬取的内容,发现搜不到。那就肯定是js动态加载的了。

2.2 安装 PhatomJS

PhatomJS 不能使用conda install 或者pip install 来安装。需要去官网下载,选择你的系统版本。下载完成的是一个压缩包,解压到你想存放的目录(我放在了“C:\Program Files” 目录)。

接下来需要把PhatomJS配置成环境变量,这样就可以直接调用了。

步骤:

1). 按win+E快捷键打开文件资源管理器“,在左侧的“此电脑”上点击右键,选择“属性”。

2). 在打开的窗口中左侧,点击“高级系统设置”。

3). 在弹出的窗口中点击“环境变量”。

4). 在新窗口中选择“PATH”,在下方点击编辑按钮。

5). 把phatomjs.exe所在的目录添加到path中即可。

这样,安装过程就搞定了。

2.3PhatomJS 的使用

在网上搜了一下,没有找到中文的官网文档,只找到了PhatomJS英文官方文档。

2.3.1 例子1

先来一个官网上的例子,PhatomJS执行js文件,输出“Hello, World!”

新建一个js文件,包含下面的代码,然后保存为hello.js。

console.log(‘Hello, world!’);

phantom.exit(); #用来终止phtomjs程序

打开cmd命令窗口,进入到hello.js 文件所在目录,执行下面的命令:

phantomjs hello.js

输出:Hello, world!

2.3.2 例子2

我们使用PhatomJS来请求一个网页,然后把网页截图保存。

创建一个js文件,包含下面的代码,保存为music.js

var page = require('webpage').create();
page.open('http://music.163.com/', function(status) {
  console.log("Status: " + status);
  if(status === "success") {
    page.render('music.png');
  }
  phantom.exit();
});

然后使用cmd命令窗口,在music.js文件所在目录,执行下面的命令:phatomjs music.js

这样会有一个屏幕截图保存在文件所在的文件夹

2.3.3 其他例子

还有一些功能就不细细展开了,知道常用的就可以继续咱们的爬虫了。有用到其他功能的童鞋们去官网看看例子。

对了,官网还有许多例子,奉上链接:Phatomjs官网栗子。

3、Selenium

3.1 简介

Selenium是一个自动化测试框架,广泛的用于自动化测试领域(是不是真的广泛用于自动化测试领域我也不知道,没怎么搞过自动化测试 -_-!,这是我臆测的 = ̄ω ̄=)。因为它能够模拟人工操作,比如能在浏览器中点击按钮、在输入框中输入文本、自动填充表单、还能进行浏览器窗口的切换、对弹出窗口进行操作。也就是说你能手动做的东西,基本都能用它来实现自动化!

该框架还支持多种语言:Java、C#、Python、Ruby、PHP、Perl、JavaScript。

当然,我们使用的是面向Python语言的API。

3.2 安装 Selenium

可以使用pip install selenium来安装。使用Anaconda3版本的童鞋们,conda install selenium在我的机器上不会下载,未找到原因。但是同样使用pip install selenium安装成功了。通过在命令行里面输入conda list,可以看到已经安装的包。

3.3 Selenium的使用

Selenium支持很多语言的使用,上面也提到了。其他语言的使用我没有接触过,直接看Python语言的使用。官网:Selenium with Python。

3.3.1 例子1

因为PhatomJS没有界面,在使用的时候没有直观感受。所以,我们暂时使用Chrome浏览器代替PhatomJS。

首先下载Chrome浏览器驱动:下载地址,选择自己的系统版本,然后下载。这是一个压缩包,解压后存到一个目录中,然后把该目录添加到环境变量。因为上面的PhatomJS已经演示过添加环境变量的流程,这里就不赘述了。

在PyCharm中输入下面代码,然后运行看看有什么反应。

from selenium import webdriver  #导入Selenium的webdriver
from selenium.webdriver.common.keys import Keys  #导入Keys

driver = webdriver.Chrome()  #指定使用的浏览器,初始化webdriver
driver.get("http://www.python.org")  #请求网页地址
assert "Python" in driver.title  #看看Python关键字是否在网页title中,如果在则继续,如果不在,程序跳出。
elem = driver.find_element_by_name("q")  #找到name为q的元素,这里是个搜索框
elem.clear()  #清空搜索框中的内容
elem.send_keys("pycon")  #在搜索框中输入pycon
elem.send_keys(Keys.RETURN)  #相当于回车键,提交
assert "No results found." not in driver.page_source  #如果当前页面文本中有“No results found.”则程序跳出
driver.close()  #关闭webdriver

3.3.2 导航操作 Navigating

导航操作的使用非常简单,完全可以通名字就知道它们是干什么的,不信先给你几个例子瞧瞧:

element = driver.find_element_by_id("passwd-id")  #通过id获取元素
element = driver.find_element_by_name("passwd")  #通过name获取元素
element = driver.find_element_by_xpath("//input[@id='passwd-id']")  #通过使用xpath匹配获取元素

下面列出详细的定位方法。有定位一个元素的,也有定位多个元素的。

  • 定位一个元素:
    find_element_by_id
    find_element_by_name
    find_element_by_xpath
    find_element_by_link_text
    find_element_by_partial_link_text
    find_element_by_tag_name
    find_element_by_class_name
    find_element_by_css_selector
    • 定位多个元素:
      find_elements_by_name
      find_elements_by_xpath
      find_elements_by_link_text
      find_elements_by_partial_link_text
      find_elements_by_tag_name
      find_elements_by_class_name
      find_elements_by_css_selector
      通过导航操作,我们就可以在找到想找的元素,然后进行接下来的处理,比如,想在一个输入框中输入数据。看3.1的那个栗子,通过导航,找到了name为p的输入框,然后使用send_keys在输入框中写入数据:
      elem.send_keys(“pycon”)
      我们知道了如何在输入框中输入数据,那如果我们碰到了下拉框该怎么办呢?
      from selenium.webdriver.support.ui import Select #导入Select
      select = Select(driver.find_element_by_name(‘name’)) #通过Select来定义该元素是下拉框
      select.select_by_index(index) #通过下拉元素的位置来选择
      select.select_by_visible_text(“text”) #通过下拉元素的内容来选择
      select.select_by_value(value) #通过下拉元素的取值来选择
      来看一个下拉框的HTML:

      结合上面的例子:
      from selenium.webdriver.support.ui import Select
      select = Select(driver.find_element_by_name(‘cars’)) #找到name为cars的select标签
      select.select_by_index(1) #下拉框选中沃尔沃
      select.select_by_visible_text(“宝马”) #下拉框选中宝马
      select.select_by_value(“benz”) #下拉框选中奥迪
      点击操作。这个很简单:
      elem.click() 在找到的元素后面加上click()就可以了。
      3.3.3 Cookies
      我们想要爬取的网站有些可能需要登录,这样就需要在请求网站的时候添加Cookies。
      driver.get(“http://www.example.com”) #先请求一个网页
      cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’} #设置cookie内容
      driver.add_cookie(cookie) #添加cookie
      3.3.4 其他
      大体思路是,用Selenium + PhatomJS 来请求网页,页面加载后模拟下拉操作,可以根据想要获取的图片多少来选择下拉的次数,然后再获取网页中的全部内容。
      五、爬虫实战改造
      1.模拟下拉操作
      要想实现网页的下拉操作,需要使用Selenium的一个方法来执行js代码。该方法如下:
      driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”)
      由此可见,使用execute_script方法可以调用JavaScript API在一个加载完成的页面中去执行js代码。可以做任何你想做的操作哦,只要用js写出来就可以了。
      改造的爬虫的第一步就是封装一个下拉方法,这个方法要能控制下拉的次数,下拉后要有等待页面加载的时间,以及做一些信息记录(在看下面的代码前自己先想一想啦):
      def scroll_down(self, driver, times):
      for i in range(times):
      print(“开始执行第”, str(i + 1),“次下拉操作”)
      driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) #执行JavaScript实现网页下拉倒底部
      print(“第”, str(i + 1), “次下拉操作执行完毕”)
      print(“第”, str(i + 1), “次等待网页加载…”)
      time.sleep(20) # 等待20秒(时间可以根据自己的网速而定),页面加载出来再执行下拉操作
      这部分做完之后就是修改页面爬取逻辑,之前是使用request 请求网址,然后找到图片url所在位置,再然后挨个请求图片url。现在,我们要首先使用Selenium 请求网址,然后模拟下拉操作,等待页面加载完毕再遵循原有逻辑挨个请求图片的url。
      逻辑部分改造如下:
      def get_pic(self):
      print(‘开始网页get请求’)
      # 使用selenium通过PhantomJS来进行网络请求
      driver = webdriver.PhantomJS()
      driver.get(self.web_url)
      self.scroll_down(driver=driver, times=5) #执行网页下拉到底部操作,执行5次
      print(‘开始获取所有a标签’)
      all_a = BeautifulSoup(driver.page_source, ‘lxml’).find_all(‘a’, class_=‘cV68d’) #获取网页中的class为cV68d的所有a标签
      print(‘开始创建文件夹’)
      self.mkdir(self.folder_path) #创建文件夹
      print(‘开始切换文件夹’)
      os.chdir(self.folder_path) #切换路径至上面创建的文件夹

              print("a标签的数量是:", len(all_a))  #这里添加一个查询图片标签的数量,来检查我们下拉操作是否有误
              for a in all_a: #循环每个标签,获取标签中图片的url并且进行网络请求,最后保存图片
                  img_str = a['style'] #a标签中完整的style字符串
                  print('a标签的style内容是:', img_str)
                  first_pos = img_str.index('"') + 1  #获取第一个双引号的位置,然后加1就是url的起始位置
                  second_pos = img_str.index('"', first_pos)  #获取第二个双引号的位置
                  img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容
      
                  #注:为了尽快看到下拉加载的效果,截取高度和宽度部分暂时注释掉,因为图片较大,请求时间较长。
                  ##获取高度和宽度的字符在字符串中的位置
                  #width_pos = img_url.index('&w=')
                  #height_pos = img_url.index('&q=')
                  #width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和宽度参数,后面用来将该参数替换掉
                  #print('高度和宽度数据字符串是:', width_height_str)
                  #img_url_final = img_url.replace(width_height_str, '')  #把高度和宽度的字符串替换成空字符
                  #print('截取后的图片的url是:', img_url_final)
      
                  #截取url中参数前面、网址后面的字符串为图片名
                  name_start_pos = img_url.index('photo')
                  name_end_pos = img_url.index('?')
                  img_name = img_url[name_start_pos : name_end_pos]
                  self.save_img(img_url, img_name) #调用save_img方法来保存图片
      

      逻辑修改完毕。执行一下,发现报错了,图片的url截取错误。那就看看到底是为什么,先输出找到url看看
      看看输出内容,如果发现通过PhatomJS 请求网页获得的url(https://blablabla) 中,没有双引号,这跟使用Chrome 看到的不一样。那就按照没有双引号的字符串重新截取图片的url。
      其实,我们只需要把url的起始位置和结束逻辑改成通过小括号来获取就可以了:
      first_pos = img_str.index(’(’) + 1 #起始位置是小括号的左边
      second_pos = img_str.index(’)’) #结束位置是小括号的右边
      好啦,这次获取图片的url就没什么问题了,程序可以顺利的执行。
      但是,细心的小伙伴儿肯定发现了,我们这个爬虫在爬取的过程中中断了,或者过一段时间网站图片更新了,我再想爬,有好多图片都是重复的,却又重新爬取一遍,浪费时间。那么我们有什么办法来达到去重的效果呢?
      2、去重
      想要做去重,无非就是在爬取图片的时候记录图片名字(图片名字是唯一的)。在爬取图片前,先检查该图片是否已经爬取过,如果没有,则继续爬取,如果已经爬取过,则不进行爬取。
      去重的逻辑如上,实现方式的不同主要体现在记录已经爬取过的信息的途径不同。比如可以使用数据库记录、使用log文件记录,或者直接检查已经爬取过的数据信息。
      根据爬取内容的不同实现方式也不同。我们这里先使用去文件夹下获取所有文件名,然后在爬取的时候做对比。
      后面的爬虫实战再使用其他方式来做去重。
      单从爬取unsplash 网站图片这个实例来看,需要知道文件夹中所有文件的名字就够了。
      Python os 模块中有两种能够获取文件夹下所有文件名的方法,分别来个示例:
      for root, dirs, files in os.walk(path):
      for file in files:
      print(file)
      for file in os.listdir(path):
      print(file)

      其中,os.walk(path) 是获取path文件夹下所有文件名和所有其子目录中的文件夹名和文件名。
      而os.listdir(path) 只是获取path文件夹下的所有文件的名字,并不care其子文件夹。
      这里我们使用os.listdir(path) 就能满足需求。
      写一个获取文件夹内所有文件名的方法:
      def get_files(self, path):
      pic_names = os.listdir(path)
      return pic_names
      因为在保存图片之前就要用到文件名的对比,所以把save_img方法修改了一下,在方法外部拼接图片名字,然后直接作为参数传入,而不是在图片存储的过程中命名:
      def save_img(self, url, file_name): ##保存图片
      print(‘开始请求图片地址,过程会有点长…’)
      img = self.request(url)
      print(‘开始保存图片’)
      f = open(file_name, ‘ab’)
      f.write(img.content)
      print(file_name,‘图片保存成功!’)
      f.close()
      为了更清晰去重逻辑,我们每次启动爬虫的时候检测一下图片存放的文件夹是否存在,如果存在则检测与文件夹中的文件做对比,如果不存在则不需要做对比(因为是新建的嘛,文件夹里面肯定什么文件都没有)。
      逻辑修改如下,是新建的则返回True,不是新建的则返回False:
      def mkdir(self, path): ##这个函数创建文件夹
      path = path.strip()
      isExists = os.path.exists(path)
      if not isExists:
      print(‘创建名字叫做’, path, ‘的文件夹’)
      os.makedirs(path)
      print(‘创建成功!’)
      return True
      else:
      print(path, ‘文件夹已经存在了,不再创建’)
      return False
      然后修改我们的爬取逻辑部分:
      主要是添加获取的文件夹中的文件名列表,然后判断图片是否存在。
      is_new_folder = self.mkdir(self.folder_path) #创建文件夹,并判断是否是新创建
      file_names = self.get_files(self.folder_path) #获取文件家中的所有文件名,类型是list
      if is_new_folder:
      self.save_img(img_url, img_name) # 调用save_img方法来保存图片
      else:
      if img_name not in file_names:
      self.save_img(img_url, img_name) # 调用save_img方法来保存图片
      else:
      print(“该图片已经存在:”, img_name, “,不再重新下载。”)
      好了,来个完整版代码(也可以去 GitHub下载):
      from selenium import webdriver #导入Selenium
      import requests
      from bs4 import BeautifulSoup #导入BeautifulSoup 模块
      import os #导入os模块
      import time

      class BeautifulPicture():
      
          def __init__(self):  #类的初始化操作
              self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1'}  #给请求指定一个请求头来模拟chrome浏览器
              self.web_url = 'https://unsplash.com'  #要访问的网页地址
              self.folder_path = 'C:\D\BeautifulPicture'  #设置图片要存放的文件目录
      
          def get_pic(self):
              print('开始网页get请求')
              # 使用selenium通过PhantomJS来进行网络请求
              driver = webdriver.PhantomJS()
              driver.get(self.web_url)
              self.scroll_down(driver=driver, times=3)  #执行网页下拉到底部操作,执行3次
              print('开始获取所有a标签')
              all_a = BeautifulSoup(driver.page_source, 'lxml').find_all('a', class_='cV68d')  #获取网页中的class为cV68d的所有a标签
              print('开始创建文件夹')
              is_new_folder = self.mkdir(self.folder_path)  #创建文件夹,并判断是否是新创建
              print('开始切换文件夹')
              os.chdir(self.folder_path)   #切换路径至上面创建的文件夹
      
              print("a标签的数量是:", len(all_a))   #这里添加一个查询图片标签的数量,来检查我们下拉操作是否有误
              file_names = self.get_files(self.folder_path)  #获取文件家中的所有文件名,类型是list
      
              for a in all_a: #循环每个标签,获取标签中图片的url并且进行网络请求,最后保存图片
                  img_str = a['style'] #a标签中完整的style字符串
                  print('a标签的style内容是:', img_str)
                  first_pos = img_str.index('(') + 1
                  second_pos = img_str.index(')')
                  img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容
      
                  # 注:为了尽快看到下拉加载的效果,截取高度和宽度部分暂时注释掉,因为图片较大,请求时间较长。
                  #获取高度和宽度的字符在字符串中的位置
                  # width_pos = img_url.index('&w=')
                  # height_pos = img_url.index('&q=')
                  # width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和宽度参数,后面用来将该参数替换掉
                  # print('高度和宽度数据字符串是:', width_height_str)
                  # img_url_final = img_url.replace(width_height_str, '')  #把高度和宽度的字符串替换成空字符
                  # print('截取后的图片的url是:', img_url_final)
      
                  #截取url中参数前面、网址后面的字符串为图片名
                  name_start_pos = img_url.index('.com/') + 5  #通过找.com/的位置,来确定它之后的字符位置
                  name_end_pos = img_url.index('?')
                  img_name = img_url[name_start_pos : name_end_pos] + '.jpg'
                  img_name = img_name.replace('/','')  #把图片名字中的斜杠都去掉
      
                  if is_new_folder:
                      self.save_img(img_url, img_name)  # 调用save_img方法来保存图片
                  else:
                      if img_name not in file_names:
                          self.save_img(img_url, img_name)  # 调用save_img方法来保存图片
                      else:
                          print("该图片已经存在:", img_name, ",不再重新下载。")
      
          def save_img(self, url, file_name): ##保存图片
              print('开始请求图片地址,过程会有点长...')
              img = self.request(url)
              print('开始保存图片')
              f = open(file_name, 'ab')
              f.write(img.content)
              print(file_name,'图片保存成功!')
              f.close()
      
          def request(self, url):  #返回网页的response
              r = requests.get(url)  # 像目标url地址发送get请求,返回一个response对象。有没有headers参数都可以。
              return r
      
          def mkdir(self, path):  ##这个函数创建文件夹
              path = path.strip()
              isExists = os.path.exists(path)
              if not isExists:
                  print('创建名字叫做', path, '的文件夹')
                  os.makedirs(path)
                  print('创建成功!')
                  return True
              else:
                  print(path, '文件夹已经存在了,不再创建')
                  return False
      
          def scroll_down(self, driver, times):
              for i in range(times):
                  print("开始执行第", str(i + 1),"次下拉操作")
                  driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  #执行JavaScript实现网页下拉倒底部
                  print("第", str(i + 1), "次下拉操作执行完毕")
                  print("第", str(i + 1), "次等待网页加载......")
                  time.sleep(30)  # 等待30秒,页面加载出来再执行下拉操作
      
          def get_files(self, path):
              pic_names = os.listdir(path)
              return pic_names
      
      beauty = BeautifulPicture()  #创建类的实例
      beauty.get_pic()  #执行类中的方法
      

      六、爬取披头士乐队历年安专辑封面网易云音乐
      实战
      上面提到过,网易云音乐的网页跟普通的网页相比主要有两点不同:

      • 网页是 js 动态加载的
      • 使用了iframe框架
        所以,
        首先,网页请求不能使用requests库,需要使用Selenium + PhatomJS。
        其次,使用Selenium + PhatomJS后,还需要针对 iframe 做特定处理。
        废话不多说,看实际操作步骤:
        首先打开网页 http://music.163.com

      在右上角的搜索框中输入“The Beatles”,然后会有一个下拉选项,选择歌手 The Beatles (红框中的内容)。

      然后看到如下页面,选择红框中的“所有专辑”,点击。

      这样就会看见所有的专辑列表,以及下方的翻页按钮。

      我们需要的就是所有专辑的图片、专辑名和专辑出版时间。看到这就可以构想一下爬虫的爬取逻辑了。定位到该页面,然后获取页码,然后挨个请求页面来爬取页面中的内容。
      点击一下翻页按钮看看url 有没有什么规律。

      点击第二页后,看到上面的地址栏!!!看到这个地址栏我都懒得翻页了。。。
      limit 参数是限制一个页面加载专辑的个数
      offset 参数是前面过滤多少个专辑,现在是一页12个专辑,所以第二页是offset=12,第三页offset=24,以此类推。。。
      一共9页,一页12个,也不到120个。So… … 改一下url 就不用翻页了!!
      limit 参数等于120,offset 参数 等于0,就搞定了! 输入下面的url,看看是不是所有的专辑都加载出来了。
      http://music.163.com/#/artist/album?id=101988&limit=120&offset=0
      下面就开始爬虫代码了。
      这里我们会用到上一篇博文中写好的几个工具方法:
      def save_img(self, url, file_name): ##保存图片
      print(‘开始请求图片地址,过程会有点长…’)
      img = self.request(url)
      print(‘开始保存图片’)
      f = open(file_name, ‘ab’)
      f.write(img.content)
      print(file_name,‘图片保存成功!’)
      f.close()

          def request(self, url):  #封装的requests 请求
              r = requests.get(url)  # 像目标url地址发送get请求,返回一个response对象。有没有headers参数都可以。
              return r
      
          def mkdir(self, path):  ##这个函数创建文件夹
              path = path.strip()
              isExists = os.path.exists(path)
              if not isExists:
                  print('创建名字叫做', path, '的文件夹')
                  os.makedirs(path)
                  print('创建成功!')
                  return True
              else:
                  print(path, '文件夹已经存在了,不再创建')
                  return False
      
          def get_files(self, path): #获取文件夹中的文件名称列表
              pic_names = os.listdir(path)
              return pic_names
      

      OK, 开始我们的爬虫逻辑部分:
      这里值得注意的是,该页面使用frame 框架,使用Selenium + PhantomJS 后并不会加载iframe 框架中的网页内容。iframe 框架相当于在页面中又加载了一个页面,需要使用Selenium 的 switch_to.frame() 方法加载(官网给的方法是switch_to_frame(),但是IDE提醒使用前面的方法替代该方法)。
      看下面的网页结构,iframe的id是“g_iframe”:

      加载 iframe 框架中的内容:
      driver = webdriver.PhantomJS()
      driver.get(self.init_url)
      driver.switch_to.frame(“g_iframe”)
      html = driver.page_source
      然后找到所有的封面元素:

      根据上图的网页结构可以看出,所有的专辑信息都在ul 标签里面,每一个专辑在一个li 标签里。li 标签中包含了图片url、专辑名字、以及专辑时间。
      抓取其中的内容就好了。
      all_li = BeautifulSoup(html, ‘lxml’).find(id=‘m-song-module’).find_all(‘li’)

      for li in all_li:
          album_img = li.find('img')['src']
          album_name = li.find('p', class_='dec')['title']
          album_date = li.find('span', class_='s-fc3').get_text()
      

      这里获取到的图片url 依然是有图片宽高参数的,所以要过滤宽高参数:
      http://p4.music.126.net/pLA1GX0KtU-vU4ZA6Cr-OQ==/1401877340532770.jpg?param=120y120
      把问号后面的参数过滤掉:
      end_pos = album_img.index(’?’) #找到问号的位置
      album_img_url = album_img[:end_pos] #截取问号之前的内容
      图片命名逻辑:专辑时间 + 专辑名。
      专辑名可能有一些特殊字符,需要替换掉!
      photo_name = album_date + ’ - ’ + album_name.replace(’/’,’’).replace(’:’,’,’) + ‘.jpg’
      再使用上一篇博文例子中的去重逻辑,修改后的爬虫逻辑部分如下:
      def spider(self):
      print(“Start!”)
      driver = webdriver.PhantomJS()
      driver.get(self.init_url)
      driver.switch_to.frame(“g_iframe”)
      html = driver.page_source

              self.mkdir(self.folder_path)  # 创建文件夹
              print('开始切换文件夹')
              os.chdir(self.folder_path)  # 切换路径至上面创建的文件夹
      
              file_names = self.get_files(self.folder_path)  # 获取文件夹中的所有文件名,类型是list
      
              all_li = BeautifulSoup(html, 'lxml').find(id='m-song-module').find_all('li')
              # print(type(all_li))
      
              for li in all_li:
                  album_img = li.find('img')['src']
                  album_name = li.find('p', class_='dec')['title']
                  album_date = li.find('span', class_='s-fc3').get_text()
                  end_pos = album_img.index('?')
                  album_img_url = album_img[:end_pos]
      
                  photo_name = album_date + ' - ' + album_name.replace('/','').replace(':',',') + '.jpg'
                  print(album_img_url, photo_name)
      
                  if photo_name in file_names:
                      print('图片已经存在,不再重新下载')
                  else:
                      self.save_img(album_img_url, photo_name)
      

      其实相对于上篇博文的例子,这个爬虫的逻辑部分还是挺简洁的。
      最后上一个完整的代码: 也可以从GitHub下载
      from selenium import webdriver
      from bs4 import BeautifulSoup
      import requests
      import os

      class AlbumCover():
      
          def __init__(self):
              self.init_url = "http://music.163.com/#/artist/album?id=101988&limit=120&offset=0" #请求网址
              self.folder_path = "C:\D\TheBeatles" #想要存放的文件目录
      
          def save_img(self, url, file_name):  ##保存图片
              print('开始请求图片地址,过程会有点长...')
              img = self.request(url)
              print('开始保存图片')
              f = open(file_name, 'ab')
              f.write(img.content)
              print(file_name, '图片保存成功!')
              f.close()
      
          def request(self, url):  # 封装的requests 请求
              r = requests.get(url)  # 像目标url地址发送get请求,返回一个response对象。有没有headers参数都可以。
              return r
      
          def mkdir(self, path):  ##这个函数创建文件夹
              path = path.strip()
              isExists = os.path.exists(path)
              if not isExists:
                  print('创建名字叫做', path, '的文件夹')
                  os.makedirs(path)
                  print('创建成功!')
                  return True
              else:
                  print(path, '文件夹已经存在了,不再创建')
                  return False
      
          def get_files(self, path):  # 获取文件夹中的文件名称列表
              pic_names = os.listdir(path)
              return pic_names
      
          def spider(self):
              print("Start!")
              driver = webdriver.PhantomJS()
              driver.get(self.init_url)
              driver.switch_to.frame("g_iframe")
              html = driver.page_source
      
              self.mkdir(self.folder_path)  # 创建文件夹
              print('开始切换文件夹')
              os.chdir(self.folder_path)  # 切换路径至上面创建的文件夹
      
              file_names = self.get_files(self.folder_path)  # 获取文件夹中的所有文件名,类型是list
      
              all_li = BeautifulSoup(html, 'lxml').find(id='m-song-module').find_all('li')
              # print(type(all_li))
      
              for li in all_li:
                  album_img = li.find('img')['src']
                  album_name = li.find('p', class_='dec')['title']
                  album_date = li.find('span', class_='s-fc3').get_text()
                  end_pos = album_img.index('?')
                  album_img_url = album_img[:end_pos]
      
                  photo_name = album_date + ' - ' + album_name.replace('/', '').replace(':', ',') + '.jpg'
                  print(album_img_url, photo_name)
      
                  if photo_name in file_names:
                      print('图片已经存在,不再重新下载')
                  else:
                      self.save_img(album_img_url, photo_name)
      
      album_cover = AlbumCover()
      album_cover.spider()
      

      执行结果:

      看看文件夹里面什么样:

      历年的专辑封面已经到手啦,还有专辑的名称和发行日期。
      总结:
      这个实战很好的运用了咱们之前讲解的知识:

      • 使用Selenium + PhatomJS 抓取动态页面
      • 使用Selenium 的switch_to.frame() 加载 iframe 中的内容
      • 使用requests 库获取图片
      • 使用BeautifulSoup 库解析抓取网页内容。
      • 使用os 库创建文件夹和获取文件夹中的文件名称列表
        八、用python爬虫框架Scrapy爬取心目中的女神
        1.介绍框架
        Scrapy,Python开发的一个快速,高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。
        Scrapy吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。
        Scratch,是抓取的意思,这个Python的爬虫框架叫Scrapy,大概也是这个意思吧,就叫它:小刮刮吧。
        Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下

      Scrapy主要包括了以下组件:

      • 引擎(Scrapy)
        用来处理整个系统的数据流处理, 触发事务(框架核心)
      • 调度器(Scheduler)
        用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
      • 下载器(Downloader)
        用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
      • 爬虫(Spiders)
        爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
      • 项目管道(Pipeline)
        负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
      • 下载器中间件(Downloader Middlewares)
        位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
      • 爬虫中间件(Spider Middlewares)
        介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
      • 调度中间件(Scheduler Middewares)
        介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
        Scrapy运行流程大概如下:
      1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
      2. 引擎把URL封装成一个请求(Request)传给下载器
      3. 下载器把资源下载下来,并封装成应答包(Response)
      4. 爬虫解析Response
      5. 解析出实体(Item),则交给实体管道进行进一步的处理
      6. 解析出的是链接(URL),则把URL交给调度器等待抓取
        2.安装
        因为python3并不能完全支持Scrapy,因此为了完美运行Scrapy,我们使用python2.7来编写和运行Scrapy。
        pip install Scrapy
        注:windows平台需要依赖pywin32,请根据自己系统32/64位选择下载安装,https://sourceforge.net/projects/pywin32
        其它可能依赖的安装包:lxml-3.6.4-cp27-cp27m-win_amd64.whl,VCForPython27.msi百度下载即可
        3.基本使用
        1.创建项目
        运行命令:scrapy startproject p1(your_project_name)
        2.自动创建目录的结果
        文件说明:
      • scrapy.cfg 项目的配置信息,主要为Scrapy命令行工具提供一个基础的配置信息。(真正爬虫相关的配置信息在settings.py文件中)

      • items.py 设置数据存储模板,用于结构化数据,如:Django的Model

      • pipelines 数据处理行为,如:一般结构化的数据持久化

      • settings.py 配置文件,如:递归的层数、并发数,延迟下载等

      • spiders 爬虫目录,如:创建文件,编写爬虫规则
        注意:一般创建爬虫文件时,以网站域名命名
        3.编写爬虫
        在spiders目录中新建xiaohuar_spider.py文件
        示例代码:

        import scrapy

        class XiaoHuarSpider(scrapy.spiders.Spider):
        name = “xiaohuar”
        allowed_domains = [“xiaohuar.com”]
        start_urls = [
        http://www.xiaohuar.com/hua/”,
        ]

          def parse(self, response):
              # print(response, type(response))
              # from scrapy.http.response.html import HtmlResponse
              # print(response.body_as_unicode())
        
              current_url = response.url #爬取时请求的url
              body = response.body  #返回的html
              unicode_body = response.body_as_unicode()#返回的html unicode编码
        

      备注:

      • 1.爬虫文件需要定义一个类,并继承scrapy.spiders.Spider

      • 2.必须定义name,即爬虫名,如果没有name,会报错。

      • 3.编写函数parse,这里需要注意的是,该函数名不能改变,因为Scrapy源码中默认callback函数的函数名就是parse;

      • 4.定义需要爬取的url,放在列表中,因为可以爬取多个url,Scrapy源码是一个For循环,从上到下爬取这些url,使用生成器迭代将url发送给下载器下载url的html。
        4.运行
        进入p1目录,运行命令 scrapy crawl xiaohuau --nolog
        格式:scrapy crawl + 爬虫名 --nolog即不显示日志
        5.scrapy查询语法:
        当我们爬取大量的网页,如果自己写正则匹配会很麻烦,也浪费时间,令人欣慰的是,scrapy内部支持更简单的查询语法,帮助我们去html中查询我们需要的标签和标签内容以及标签属性。下面逐一进行介绍:

      • 查询子子孙孙中的某个标签(以div标签为例)://div

      • 查询儿子中的某个标签(以div标签为例):/div

      • 查询标签中带有某个class属性的标签://div[@class=’c1′]即子子孙孙中标签是div且class=‘c1’的标签

      • 查询标签中带有某个class=‘c1’并且自定义属性name=‘alex’的标签://div@class=’c1′

      • 查询某个标签的文本内容://div/span/text() 即查询子子孙孙中div下面的span标签中的文本内容

      • 查询某个属性的值(例如查询a标签的href属性)://a/@href
        注:urllib.urlretrieve(ab_src, file_path) ,接收文件路径和需要保存的路径,会自动去文件路径下载并保存到我们指定的本地路径。
        5.递归爬取网页
        上述代码仅仅实现了一个url的爬取,如果该url的爬取的内容中包含了其他url,而我们也想对其进行爬取,那么如何实现递归爬取网页呢?
        通过yield生成器向每一个url发送request请求,并执行返回函数parse,从而递归获取校花图片和校花姓名学校等信息。
        注:可以修改settings.py 中的配置文件,以此来指定“递归”的层数,如: DEPTH_LIMIT = 1
        6.scrapy查询语法中的正则:

      • 语法规则:Selector(response=response查询对象).xpath(‘//li[re:test(@class, “item-d”)]//@href’).extract(),即根据re正则匹配,test即匹配,属性名是class,匹配的正则表达式是”item-d”,然后获取该标签的href属性。
        选择器规则Demo
        def parse(self, response):
        from scrapy.http.cookies import CookieJar
        cookieJar = CookieJar()
        cookieJar.extract_cookies(response, response.request)
        print(cookieJar._cookies)
        获取响应cookie
        更多选择器规则:http://scrapy-chs.readthedocs.io/zh_CN/latest/topics/selectors.html
        7.格式化处理
        上述实例只是简单的图片处理,所以在parse方法中直接处理。如果对于想要获取更多的数据(获取页面的价格、商品名称、QQ等),则可以利用Scrapy的items将数据格式化,然后统一交由pipelines来处理。即不同功能用不同文件实现。
        items:即用户需要爬取哪些数据,是用来格式化数据,并告诉pipelines哪些数据需要保存。
        示例items.py文件:
        import scrapy
        class JieYiCaiItem(scrapy.Item):
        company = scrapy.Field()
        title = scrapy.Field()
        qq = scrapy.Field()
        info = scrapy.Field()
        more = scrapy.Field()
        即:需要爬取所有url中的公司名,title,qq,基本信息info,更多信息more。
        上述定义模板,以后对于从请求的源码中获取的数据同样按照此结构来获取,所以在spider中需要有一下操作:
        import scrapy
        import hashlib
        from beauty.items import JieYiCaiItem
        from scrapy.http import Request
        from scrapy.selector import HtmlXPathSelector
        from scrapy.spiders import CrawlSpider, Rule
        from scrapy.linkextractors import LinkExtractor
        class JieYiCaiSpider(scrapy.spiders.Spider):
        count = 0
        url_set = set()

          name = "jieyicai"
          domain = 'http://www.jieyicai.com'
          allowed_domains = ["jieyicai.com"]
          start_urls = [
              "http://www.jieyicai.com",
          ]
          rules = [
              #下面是符合规则的网址,但是不抓取内容,只是提取该页的链接(这里网址是虚构的,实际使用时请替换)
              #Rule(SgmlLinkExtractor(allow=(r'http://test_url/test?page_index=\d+'))),
              #下面是符合规则的网址,提取内容,(这里网址是虚构的,实际使用时请替换)
              #Rule(LinkExtractor(allow=(r'http://www.jieyicai.com/Product/Detail.aspx?pid=\d+')), callback="parse"),
          ]
          def parse(self, response):
              md5_obj = hashlib.md5()
              md5_obj.update(response.url)
              md5_url = md5_obj.hexdigest()
              if md5_url in JieYiCaiSpider.url_set:
                  pass
              else:
                  JieYiCaiSpider.url_set.add(md5_url)
                  
                  hxs = HtmlXPathSelector(response)
                  if response.url.startswith('http://www.jieyicai.com/Product/Detail.aspx'):
                      item = JieYiCaiItem()
                      item['company'] = hxs.select('//span[@class="username g-fs-14"]/text()').extract()
                      item['qq'] = hxs.select('//span[@class="g-left bor1qq"]/a/@href').re('.*uin=(?P<qq>\d*)&')
                      item['info'] = hxs.select('//div[@class="padd20 bor1 comard"]/text()').extract()
                      item['more'] = hxs.select('//li[@class="style4"]/a/@href').extract()
                      item['title'] = hxs.select('//div[@class="g-left prodetail-text"]/h2/text()').extract()
                      yield item
                  current_page_urls = hxs.select('//a/@href').extract()
                  for i in range(len(current_page_urls)):
                      url = current_page_urls[i]
                      if url.startswith('/'):
                          url_ab = JieYiCaiSpider.domain + url
                          yield Request(url_ab, callback=self.parse)
        

        spider
        spider
        spider
        上述代码中:对url进行md5加密的目的是避免url过长,也方便保存在缓存或数据库中。
        此处代码的关键在于:

      • 将获取的数据封装在了Item对象中

      • yield Item对象 (一旦parse中执行yield Item对象,则自动将该对象交个pipelines的类来处理),
        import json
        from twisted.enterprise import adbapi
        import MySQLdb.cursors
        import re

        mobile_re = re.compile(r’(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}’)
        phone_re = re.compile(r’(\d±\d+|\d+)’)

        class JsonPipeline(object):

          def __init__(self):
              self.file = open('/Users/wupeiqi/PycharmProjects/beauty/beauty/jieyicai.json', 'wb')
        
        
          def process_item(self, item, spider):
              line = "%s  %s\n" % (item['company'][0].encode('utf-8'), item['title'][0].encode('utf-8'))
              self.file.write(line)
              return item
        

        class DBPipeline(object):

          def __init__(self):
              self.db_pool = adbapi.ConnectionPool('MySQLdb',
                                                   db='DbCenter',
                                                   user='root',
                                                   passwd='123',
                                                   cursorclass=MySQLdb.cursors.DictCursor,
                                                   use_unicode=True)
          def process_item(self, item, spider):
              query = self.db_pool.runInteraction(self._conditional_insert, item)
              query.addErrback(self.handle_error)
              return item
          def _conditional_insert(self, tx, item):
              tx.execute("select nid from company where company = %s", (item['company'][0], ))
              result = tx.fetchone()
              if result:
                  pass
              else:
                  phone_obj = phone_re.search(item['info'][0].strip())
                  phone = phone_obj.group() if phone_obj else ' '
                  mobile_obj = mobile_re.search(item['info'][1].strip())
                  mobile = mobile_obj.group() if mobile_obj else ' '
                  values = (
                      item['company'][0],
                      item['qq'][0],
                      phone,
                      mobile,
                      item['info'][2].strip(),
                      item['more'][0])
                  tx.execute("insert into company(company,qq,phone,mobile,address,more) values(%s,%s,%s,%s,%s,%s)", values)
          def handle_error(self, e):
              print 'error',e 
        

        pipelines
        pipelines
        上述代码中多个类的目的是,可以同时保存在文件和数据库中,保存的优先级可以在配置文件settings中定义。
        ITEM_PIPELINES = {
        ‘beauty.pipelines.DBPipeline’: 300,
        ‘beauty.pipelines.JsonPipeline’: 100,
        }
        注释: 每行后面的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。

猜你喜欢

转载自blog.csdn.net/m0_37906230/article/details/83994604