这一文砸门就打开爬虫世界的大门,案例教学
首先,咱先看下爬虫的定义:网络爬虫(又称为网页蜘蛛,网络机器人 (opens new window),在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。一句话概括就是网页信息搬运工。(原则上,只要是浏览器(客户端)能做的事情,爬虫都能够做。)
我们平时都说Python爬虫,其实这里可能有个误解,爬虫并不是Python独有的,可以做爬虫的语言有很多例如:PHP,JAVA,C#,C++,Python,选择Python做爬虫是因为Python相对来说比较简单,而且功能比较齐全。Python渐渐成为了写很多人写爬虫的第一选择
- 开发效率高,代码简洁,一行代码就可完成请求,100行可以完成一个复杂的爬虫任务;
- 爬虫对于代码执行效率要求不高,网站IO才是最影响爬虫效率的。如一个网页请求可能需要100ms,数据处理10ms还是1ms影响不大;
- 非常多优秀的第三方库,如requests,beautifulsoup,selenium等等;
当然实践来源于理论,做爬虫前肯定要先了解相关的规则和原理,要知道互联网可不是法外之地,别被抓了
爬虫应该遵循的规则:
robots协议是一种存放于网站根目录下的ASCII编码 (opens new window)的文本文件,它通常告诉网络搜索引擎的漫游器(又称网络蜘蛛),此网站中的哪些内容是不应被搜索引擎的漫游器获取的,哪些是可以被漫游器获取的。一句话概括就是告诉你哪些东西能爬哪些不能爬。 以淘宝为例——https://www.taobao.com/robots.txt
首先是环境的搭建
- Python安装,
- pip安装,pip是Python的包管理器,现在的Python安装包一般都会自带pip,不需要自己再去额外安装了;
- requests,beautifulsoup库的安装,通过以下语句来完成安装:
pip install requests
pip install beautifulsoup4
- 谷歌浏览器(chrome);
- 编译器:用Pycharm / VSCode 都可以
第三方库介绍
# requests
- 官方中文文档:https://2.python-requests.org/zh_CN/latest/
requests
应该是用Python写爬虫用到最多的库了,同时requests
也是目前Github上star✨最多的Python开源项目。
requests
在爬虫中一般用于来处理网络请求,接下来会用通过简单的示例来展示requests
的基本用法。
- 首先我们需要倒入
requests
模块;
import requests
- 接着我们尝试向baidu发起请求;
r = requests.get('https://www.baidu.com/')
- 我们现在获得来命名为
r
的response对象,从这个对象中我们便可以获取到很多信息,如:
- 状态码,
200
即为请求成功 - 页面Html5代码
# 返回请求状态码,200即为请求成功
print(r.status_code)
# 返回页面代码
print(r.text)
# 对于特定类型请求,如Ajax请求返回的json数据
print(r.json())
- 当然对于大部分网站都会需要你表明你的身份,我们一般正常访问网站都会附带一个请求头(headers)信息,里面包含了你的浏览器,编码等内容,网站会通过这部分信息来判断你的身份,所以我们一般写爬虫也加上一个headers;
# 添加headers
headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'}
r = requests.get('https://www.baidu.com/', headers=headers)
- 针对
post
请求,也是一样简单;
# 添加headers
headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'}
# post请求
data = {'users': 'abc', 'password': '123'}
r = requests.post('https://www.weibo.com', data=data, headers=headers)
- 很多时候等于需要登录的站点我们可能需要保持一个会话,不然每次请求都先登录一遍效率太低,在
requests
里面一样很简单;
# 保持会话
# 新建一个session对象
sess = requests.session()
# 先完成登录
sess.post('maybe a login url', data=data, headers=headers)
# 然后再在这个会话下去访问其他的网址
sess.get('other urls')
# beautifulsoup
当我们通过requests
获取到整个页面的html5代码之后,我们还得进一步处理,因为我们需要的往往只是整个页面上的一小部分数据,所以我们需要对页面代码html5解析然后筛选提取出我们想要对数据,这时候beautifulsoup
便派上用场了。
beautifulsoup
之后通过标签+属性的方式来进行定位,譬如说我们想要百度的logo,我们查看页面的html5代码,我们可以发现logo图片是在一个div
的标签下,然后class=index-logo-srcnew
这个属性下。
所以我们如果需要定位logo图片的话便可以通过div
和class=index-logo-srcnew
来进行定位。
下面也会提供一些简单的示例来说明beautifulsoup
的基本用法:
- 导入beautifulsou模块;
from bs4 import BeautifulSoup
- 对页面代码进行解析,这边选用对html代码是官方示例中使用的***爱丽丝***页面代码;
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><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>
"""
# 选用lxml解析器来解析
soup = BeautifulSoup(html, 'lxml') # 记得按照lxml库 pip install lxml
# 如果你不想使用 lxml 解析器,你也可以选择使用 html.parser
soup = BeautifulSoup(html, 'html.parser')
- 我们现在获得一个命名为
soup
的Beautifulsoup
对象,从这个对象中我们便能定位出我们想要的信息,如:
# 获取标题
print(soup.title)
# 获取文本
print(soup.title.text)
# 通过标签定位
print(soup.find_all('a'))
# 通过属性定位
print(soup.find_all(attrs={'id': 'link1'}))
# 标签 + 属性定位
print(soup.find_all('a', id='link1'))
打印结果如下:
<title>The Dormouse's story</title>
The Dormouse's story
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
下面弄一个案例尝尝鲜
# 获取17173新游频道 (opens new window)下游戏名
- 定位我们所需要的信息,记住html里面的位置。
这边可以分享一个小技巧,以前我刚开始写爬虫的时候,寻找代码里面的信息都是先去把整个页面给down下来,然后再在里面Ctrl+F查找,其实大部分浏览器都提供了很简单的方法来定位页面代码位置的,这边会以Chrome浏览器为例。
步骤:
- F12打开控制台,选择
element
标签查看页面代码; - 点击控制台左上角箭头,然后点击页面上我们需要的信息,我们可以看到控制台中页面代码直接跳转到对应的位置;
- 页面代码中一直向上选择标签直至囊括我们需要的所有信息;
- 记住此时的标签以及熟悉等信息,这将会用于后面解析筛选数据。
- 接下来便可以开始敲代码了,完整代码如下,对于每个步骤均有详细的注释:
from bs4 import BeautifulSoup
import requests
# 页面url地址
url = 'http://newgame.17173.com/game-list-0-0-0-0-0-0-0-0-0-0-1-2.html'
# 发送请求,r为页面响应
r = requests.get(url)
# r.text获取页面代码
# 使用lxml解析器解析页面代码
soup = BeautifulSoup(r.text, 'lxml')
# 两次定位,先找到整个信息区域
info_list = soup.find(attrs={'class': 'ptlist ptlist-pc'})
# 在此区域内获取游戏名,find_all返回的是list
tit_list = info_list.find_all(attrs={'class': 'tit'})
# 遍历获取游戏名
# .text可获取文本内容,替换掉文章中的换行符
for title in tit_list:
print(title.text.replace('\n', ''))
结果:
剑御天下暂未评分
苏丹的游戏暂未评分
沙威玛传奇暂未评分
天涯明月刀手游-赛季版暂未评分
漫威 神秘混乱暂未评分
息风谷战略暂未评分
三国志·天下归心暂未评分
......
有些知识来需要补充
异常的处理和HTTP状态码的分类
# 获取拉勾网职位信息
目前很多网站上的信息都是通过Ajax动态加载的,譬如当你翻看某电商网站的评论,当你点击下一页的时候,网址并没发生变化,但上面的评论都变了,这其实就是通过Ajax动态加载出来的。
这里的下一页➡️按钮并不是只想另外一个页面,而是会在后台发送一个请求,服务器接收到这个请求之后会在当前页面上渲染出来。
其实我自己是比较偏爱爬这种类型的数据的,因为统计Ajax请求返回来的数据都是非常规整的json
数据,不需要我们去写复杂的表达式去解析了。
接下来我们将会通过一个拉勾网职位信息的爬虫来说明这类网站的爬取流程:
- F12打开控制台,然后搜索‘数据分析’,注意一定是先打开控制台,然后再去搜索,不然请求信息是没有记录下来的。
- 然后我们去
Network
标签下的XHR
下查找我们需要的请求(动态加载的数请求都是在XHR
下); - 然后我们切换到
headers
标签下,我们可以看到请求的地址和所需到参数等信息; - 实验几次之后我们便能发现这三个参数的含义分别是:
- first:是否首页
- pn:页码
- kd:搜索关键词
- 正常来说我们直接向这个网址传
first
,pn
,kd
三个参数就好了,不过尝试了几次之后发现拉勾有如下比较有意思的限制:
- headers里面referer参数是必须的,referer是向服务器表示你是从哪个页面跳转过来的;
- 必须得先访问这个referer的网址,然后再去请求职位信息的API。
代码如下,也很简单
import requests
class Config:
kd = '数据分析'
referer = 'https://www.lagou.com/jobs/list_%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90?labelWords=&fromSearch=true&suginput='
headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Referer': referer,
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/'
'537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'}
class Spider:
def __init__(self, kd=Config.kd):
self.kd = kd
self.url = Config.referer
self.api = 'https://www.lagou.com/jobs/positionAjax.json'
# 必须先请求referer网址
self.sess = requests.session()
self.sess.get(self.url, headers=Config.headers)
def get_position(self, pn):
data = {'first': 'true',
'pn': str(pn),
'kd': self.kd
}
# 向API发起POST请求
r = self.sess.post(self.api, headers=Config.headers, data=data)
# 直接.json()解析数据
return r.json()['content']['positionResult']['result']
def engine(self, total_pn):
for pn in range(1, total_pn + 1):
results = self.get_position(pn)
for pos in results:
print(pos['positionName'], pos['companyShortName'], pos['workYear'], pos['salary'])
if __name__ == '__main__':
lagou = Spider()
lagou.engine(2)
其实爬虫还需要根据自己的需求安装其他库的
import csv #用于把爬取的数据存储为csv格式,可以excel直接打开的
import time #用于对请求加延时,爬取速度太快容易被反爬
from time import sleep #同上
import random #用于对延时设置随机数,尽量模拟人的行为
import requests #用于向网站发送请求
from lxml import etree #lxml为第三方网页解析库,强大且速度快
然后再爬爬取豆瓣评分电影Top250的数据
爬取的内容是:电影详情链接,图片链接,影片中文名,影片外国名,评分,评价数,概况,相关信息。
from bs4 import BeautifulSoup # 网页解析,获取数据
import re # 正则表达式,进行文字匹配`
import urllib.request, urllib.error # 制定URL,获取网页数据
import xlwt # 进行excel操作
#import sqlite3 # 进行SQLite数据库操作
findLink = re.compile(r'<a href="(.*?)">') # 创建正则表达式对象,标售规则 影片详情链接的规则
findImgSrc = re.compile(r'<img.*src="(.*?)"', re.S)
findTitle = re.compile(r'<span class="title">(.*)</span>')
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>')
findJudge = re.compile(r'<span>(\d*)人评价</span>')
findInq = re.compile(r'<span class="inq">(.*)</span>')
findBd = re.compile(r'<p class="">(.*?)</p>', re.S)
def main():
baseurl = "https://movie.douban.com/top250?start=" #要爬取的网页链接
# 1.爬取网页
datalist = getData(baseurl)
savepath = "豆瓣电影Top250.xls" #当前目录新建XLS,存储进去
# dbpath = "movie.db" #当前目录新建数据库,存储进去
# 3.保存数据
saveData(datalist,savepath) #2种存储方式可以只选择一种
# saveData2DB(datalist,dbpath)
# 爬取网页
def getData(baseurl):
datalist = [] #用来存储爬取的网页信息
for i in range(0, 10): # 调用获取页面信息的函数,10次
url = baseurl + str(i * 25)
html = askURL(url) # 保存获取到的网页源码
# 2.逐一解析数据
soup = BeautifulSoup(html, "html.parser")
for item in soup.find_all('div', class_="item"): # 查找符合要求的字符串
data = [] # 保存一部电影所有信息
item = str(item)
link = re.findall(findLink, item)[0] # 通过正则表达式查找
data.append(link)
imgSrc = re.findall(findImgSrc, item)[0]
data.append(imgSrc)
titles = re.findall(findTitle, item)
if (len(titles) == 2):
ctitle = titles[0]
data.append(ctitle)
otitle = titles[1].replace("/", "") #消除转义字符
data.append(otitle)
else:
data.append(titles[0])
data.append(' ')
rating = re.findall(findRating, item)[0]
data.append(rating)
judgeNum = re.findall(findJudge, item)[0]
data.append(judgeNum)
inq = re.findall(findInq, item)
if len(inq) != 0:
inq = inq[0].replace("。", "")
data.append(inq)
else:
data.append(" ")
bd = re.findall(findBd, item)[0]
bd = re.sub('<br(\s+)?/>(\s+)?', "", bd)
bd = re.sub('/', "", bd)
data.append(bd.strip())
datalist.append(data)
return datalist
# 得到指定一个URL的网页内容
def askURL(url):
head = { # 模拟浏览器头部信息,向豆瓣服务器发送消息
"User-Agent": "Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 80.0.3987.122 Safari / 537.36"
}
# 用户代理,表示告诉豆瓣服务器,我们是什么类型的机器、浏览器(本质上是告诉浏览器,我们可以接收什么水平的文件内容)
request = urllib.request.Request(url, headers=head)
html = ""
try:
response = urllib.request.urlopen(request)
html = response.read().decode("utf-8")
except urllib.error.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
return html
# 保存数据到表格
def saveData(datalist,savepath):
print("save.......")
book = xlwt.Workbook(encoding="utf-8",style_compression=0) #创建workbook对象
sheet = book.add_sheet('豆瓣电影Top250', cell_overwrite_ok=True) #创建工作表
col = ("电影详情链接","图片链接","影片中文名","影片外国名","评分","评价数","概况","相关信息")
for i in range(0,8):
sheet.write(0,i,col[i]) #列名
for i in range(0,250):
# print("第%d条" %(i+1)) #输出语句,用来测试
data = datalist[i]
for j in range(0,8):
sheet.write(i+1,j,data[j]) #数据
book.save(savepath) #保存
# def saveData2DB(datalist,dbpath):
# init_db(dbpath)
# conn = sqlite3.connect(dbpath)
# cur = conn.cursor()
# for data in datalist:
# for index in range(len(data)):
# if index == 4 or index == 5:
# continue
# data[index] = '"'+data[index]+'"'
# sql = '''
# insert into movie250(
# info_link,pic_link,cname,ename,score,rated,instroduction,info)
# values (%s)'''%",".join(data)
# # print(sql) #输出查询语句,用来测试
# cur.execute(sql)
# conn.commit()
# cur.close
# conn.close()
# def init_db(dbpath):
# sql = '''
# create table movie250(
# id integer primary key autoincrement,
# info_link text,
# pic_link text,
# cname varchar,
# ename varchar ,
# score numeric,
# rated numeric,
# instroduction text,
# info text
# )
#
#
# ''' #创建数据表
# conn = sqlite3.connect(dbpath)
# cursor = conn.cursor()
# cursor.execute(sql)
# conn.commit()
# conn.close()
# 保存数据到数据库
if __name__ == "__main__": # 当程序执行时
# 调用函数
main()
# init_db("movietest.db")
print("爬取完毕!")
会在一边生成xls文件的
自己来一遍,爬一下自己的QQ音乐历史歌曲