注重版权,转载请注明原作者和原文链接
作者:码农BookSea
原文链接:https://blog.csdn.net/bookssea/article/details/
先看后赞,养成习惯。
点赞收藏,人生辉煌。

讲解我们的爬虫之前,先概述关于爬虫的简单概念(毕竟是零基础教程)
爬虫
网络爬虫(又被称为网页蜘蛛,网络机器人)就是模拟浏览器发送网络请求,接收请求响应,一种按照一定的规则,自动地抓取互联网信息的程序。
原则上,只要是浏览器(客户端)能做的事情,爬虫都能够做。
为什么我们要使用爬虫
互联网大数据时代,给予我们的是生活的便利以及海量数据爆炸式的出现在网络中。
过去,我们通过书籍、报纸、电视、广播获取信息,这些信息数量有限,且是经过一定的筛选,信息相对而言比较有效,但是缺点则是信息面太过于狭窄了。不对称的信息传导,以致于我们视野受限,无法了解到更多的信息和知识。
互联网大数据时代,我们突然间信息获取自由了,我们得到了海量的信息,但是大多数都是无效的垃圾信息。
例如新浪微博,一天产生数亿条的状态更新,而在百度搜索引擎中,随意搜一条——减肥100,000,000条信息。
在如此海量的信息碎片中,我们如何获取对自己有用的信息呢?
答案是筛选!
通过某项技术将相关的内容收集起来,在分析删选才能得到我们真正需要的信息。
这个信息收集分析整合的工作,可应用的范畴非常的广泛,无论是生活服务、出行旅行、金融投资、各类制造业的产品市场需求等等……都能够借助这个技术获取更精准有效的信息加以利用。
网络爬虫技术,虽说有个诡异的名字,让能第一反应是那种软软的蠕动的生物,但它却是一个可以在虚拟世界里,无往不前的利器。
爬虫准备工作
我们平时都说Python爬虫,其实这里可能有个误解,爬虫并不是Python独有的,可以做爬虫的语言有很多例如:PHP,JAVA,C#,C++,Python,选择Python做爬虫是因为Python相对来说比较简单,而且功能比较齐全。
首先我们需要下载python,我下载的是官方最新的版本 3.8.3
其次我们需要一个运行Python的环境,我用的是pychram

也可以从官方下载,我们还需要一些库来支持爬虫的运行(有些库Python可能自带了)
差不多就是这几个库了,良心的我已经在后面写好注释了

(爬虫运行过程中,不一定就只需要上面几个库,看你爬虫的一个具体写法了,反正需要库的话我们可以直接在setting里面安装)
爬虫项目讲解
我做的是爬取豆瓣评分电影Top250的爬虫代码
我们要爬取的就是这个网站:https://movie.douban.com/top250
这边我已经爬取完毕,给大家看下效果图,我是将爬取到的内容存到xls中
我们的爬取的内容是:
电影详情链接,图片链接,影片中文名,影片外国名,评分,评价数,概况,相关信息。
代码分析
先把代码发放上来,然后我根据代码逐步解析
# -*- codeing = utf-8 -*-from bs4 import BeautifulSoup # 网页解析,获取数据
import re # 正则表达式,进行文字匹配`import urllib.request, urllib.error # 制定URL,获取网页数据import xlwt
# 进行excel操作#import sqlite3 # 进行SQLite数据库操作
findLink = re.compile(r'<a href=https://www.360doc.cn/article/"(.*?)">') # 创建正则表达式对象,标售规则 影片详情链接的规则
findImgSrc = re.compile(r'<img.*src="(.*?)"', re.S)
findTitle = re.compile(r'(.*)</span>')
findRating = re.compile(r'
findJudge = re.compile(r'(d*)人评价</span>')
findInq = re.compile(r'<(.*)</span>')
findBd = re.compile(r'(.*?)', 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("爬取完毕!")
下面我根据代码,从下到下给大家讲解分析一遍

-- codeing = utf-8 --,开头的这个是设置编码为utf-8 ,写在开头,防止乱码。
然后下面
import
就是导入一些库,做做准备工作,(sqlite3这库我并没有用到所以我注释起来了)。
下面一些
find
开头的是正则表达式,是用来我们筛选信息的。
(正则表达式用到 re 库,也可以不用正则表达式,不是必须的。)
大体流程分三步走:
1. 爬取网页
2.逐一解析数据
3. 保存网页
先分析流程1,爬取网页,baseurl 就是我们要爬虫的网页网址,往下走,调用了 getData(baseurl) ,
我们来看 getData方法
for i in range(0, 10): # 调用获取页面信息的函数,10次
url = baseurl + str(i * 25)
这段大家可能看不懂,其实是这样的:
因为电影评分Top250,每个页面只显示25个,所以我们需要访问页面10次,25*10=250。
baseurl = "https://movie.douban.com/top250?start="
我们只要在baseurl后面加上数字就会跳到相应页面,
比如i=1时
https://movie.douban.com/top250?start=25
我放上超链接,大家可以点击看看会跳到哪个页面,毕竟实践出真知。

然后又调用了askURL来请求网页,这个方法是请求网页的主体方法,
怕大家翻页麻烦,我再把代码复制一遍,让大家有个直观感受
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
这个askURL就是用来向网页发送请求用的,
那么这里就有老铁问了,为什么这里要写个head呢?

这是因为我们要是不写的话,访问某些网站的时候会被认出来爬虫,显示错误,错误代码
418
这是一个梗大家可以百度下,
418 I’m a teapot
The HTTP 418 I’m a teapot client error response code indicates that
the server refuses to brew coffee because it is a teapot. This error
is a reference to Hyper Text Coffee Pot Control Protocol which was an
April Fools’ joke in 1998.
我是一个茶壶

所以我们需要 “装” ,装成我们就是一个浏览器,这样就不会被认出来,
伪装一个身份。

来,我们继续往下走,
html = response.read().decode("utf-8")
这段就是我们读取网页的内容,设置编码为utf-8,目的就是为了防止乱码。
访问成功后,来到了第二个流程:
2.逐一解析数据
解析数据这里我们用到了 BeautifulSoup(靓汤) 这个库,这个库是几乎是做爬虫必备的库,无论你是什么写法。
下面就开始查找符合我们要求的数据,用BeautifulSoup的方法以及 re 库的
正则表达式去匹配,
findLink = re.compile(r'<a href=https://www.360doc.cn/article/"(.*?)">') # 创建正则表达式对象,标售规则 影片详情链接的规则
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)
匹配到符合我们要求的数据,然后存进
dataList
, 所以
dataList
里就存放着我们需要的数据了。
最后一个流程:
3.保存数据
# 3.保存数据
saveData(datalist,savepath) #2种存储方式可以只选择一种#
saveData2DB(datalist,dbpath)
保存数据可以选择保存到 xls 表, 需要(xlwt库支持)
也可以选择保存数据到 sqlite数据库, 需要(sqlite3库支持)
这里我选择保存到 xls 表 ,这也是为什么我注释了一大堆代码,注释的部分就是保存到 sqlite 数据库的代码,二者选一就行
保存到 xls 的主体方法是 saveData
(下面的saveData2DB方法是保存到sqlite数据库)
:
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) #保存
创建工作表,创列(会在当前目录下创建),
sheet = book.add_sheet('豆瓣电影Top250', cell_overwrite_ok=True) #创建工作表
col = ("电影详情链接","图片链接","影片中文名","影片外国名","评分","评价数","概况","相关信息")
然后把 dataList里的数据一条条存进去就行。
最后运作成功后,会在左侧生成这么一个文件

打开之后看看是不是我们想要的结果

成了,成了!

如果我们需要以数据库方式存储,可以先生成 xls 文件,再把 xls 文件导入数据库中,就可以啦
本篇文章讲解到这里啦,我感觉我讲的还算细致吧,爬虫我也是最近才可以学,对这个比较有兴趣,我肯定有讲的不好的地方,欢迎各位大佬来指正我 。
网络爬虫
网络爬虫(web crawler), 以前经常称为网络蜘蛛(spider), 是按照一定的规则自动浏览万维网并获取信息的机器人程序(或叫脚本), 曾经被广泛的应用于互联网搜索引擎. 使用过互联网和浏览器的人都知道, 网页中除了提供用户阅读的文字信息之外, 还包含一些超链接. 网络爬虫系统正是通过网页中的超链接信息不断获得网络上的其他页面. 正因为如此, 网络数据采集的过程就像一个爬虫或者蜘蛛在网络上漫游, 所有才被形象的称之为网络爬虫或者网络蜘蛛.
爬虫的应用领域
在理想的状态下, 所有的ICP(internet Content Provider) 都应该为自己的网络提供API接口来共享它们允许其他程序获取的数据, 在这种情况下爬虫就不是必需品, 国内比较有名的电商平台(如淘宝, 京东等), 社交平台(如/微博/微信等)这些网站都提供了自己的Open Api, 但是这类Open Api通常会对可以抓取的数据频率进行限制. 对于大多数的公司而言, 计时的获取行业相关数据就是企业生存的重要环节之一, 然而大部分企业在行业数据方面的匮乏是其与生俱来的短板, 合理的利用爬虫来获取数据并从中提取出有价值的信息是至关重要的. 当然爬虫还有很多重要的应用灵玉, 以下列举了其中一部分.
1. 搜索引擎
2. 新闻聚合
3. 社交应用
4. 舆情监控
5. 行业数据
合法性和背景调研
爬虫合法性探讨
1.网络爬虫领域目前还属于拓荒阶段, 虽然互联网世界已经通过自己的游戏规则建立起一定的道德规范(Robots协议, 全称是'网络爬虫排除标准'), 但在法律部分还在建立和完善中, 也就是说, 现在这个领域暂时还是灰色地带.
2. '法不禁止即为许可', 如果爬虫就像浏览器一样获取的是前端显示的数据(网页上的***息), 而不是网站后台的私密敏感信息, 就不太担心法律法规的约束, 因为目前大数据产业链的发展速度远远超过了法律的完善程度.
3. 在爬取网站的时候, 需要限制自己的爬虫遵守Robots协议, 同时控制网络爬虫程序的抓取数据的速度, 在使用数据的时候, 必须要尊重网站的知识产权(从Web2.0时代开始, 虽然Web上的数据很多都是由用户提供的, 但是网站平台是投入了运营成本的, 当用户在注册和发布呢日用时, 平台通常就已经获取了对数据的所有权, 使用权和分发权). 如果违反了这些规定, 在打官司的时候败诉几率就非常高.
Rbots.txt文件
大多数网站都会定义robots.txt文件, 下面以淘宝的robots.txt文件为例, 看看该网站对爬虫有哪些限制
User-agent: Baiduspider
Allow: /article
Allow: /oshtml
Disallow: /product/
Disallow: /
User-Agent: Googlebot
Allow: /article
Allow: /oshtml
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Disallow: /
User-agent: Bingbot
Allow: /article
Allow: /oshtml
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Disallow: /
User-Agent: 360Spider
Allow: /article
Allow: /oshtml
Disallow: /
User-Agent: Yisouspider
Allow: /article
Allow: /oshtml
Disallow: /
User-Agent: Sogouspider
Allow: /article
Allow: /oshtml
Allow: /product
Disallow: /
User-Agent: Yahoo! Slurp
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Disallow: /
User-Agent: *
Disallow: /
注意上面robots.txt第一段的最后一行, 通过设置’Disallow:/’禁止百度爬虫访问除了’Allow’规定页面外的其他所有页面. 因此当你在百度搜索’淘宝’的时候, 搜索结果下方会出现: '由于该网站的rebots.txt文件存在限制指令(限制搜索引擎抓取). 系统无法提供该页面的内容描述.’, 百度作为一个搜索引擎, 至少在表面上遵守了淘宝网的robots.txt协议, 所以用户不能从百度上搜索到淘宝内部的产品信息.

相关工具介绍
在开始讲解爬虫之前,我们稍微对HTTP(超文本传输协议)做一些回顾,因为我们在网页上看到的内容通常是浏览器执行HTML语言得到的结果,而HTTP就是传输HTML数据的协议。HTTP是构建于TCP(传输控制协议)之上应用级协议,它利用了TCP提供的可靠的传输服务实现了Web应用中的数据交换。按照维基百科上的介绍,设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法,也就是说这个协议是浏览器和Web服务器之间传输的数据的载体。关于这个协议的详细信息以及目前的发展状况,大家可以阅读阮一峰老师的《HTTP 协议入门》、《互联网协议入门》系列以及《图解HTTPS协议》进行了解,下图是我在2009年9月10日凌晨4点在四川省网络通信技术重点实验室用开源协议分析工具Ethereal(抓包工具WireShark的前身)截取的访问百度首页时的HTTP请求和响应的报文(协议数据),由于Ethereal截取的是经过网络适配器的数据,因此可以清晰的看到从物理链路层到应用层的协议数据。
HTTP请求(请求行+请求头+空行+[消息体]):

HTTP响应(响应行+响应头+空行+消息体):

相关工具
Chrome Developer Tools
开发推荐使用谷歌浏览器, 这是谷歌的开发者工具
POSTMAN(邮差)

HTTPie
$ http --header http://www.scu.edu.cn
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, max-age=600
Connection: Keep-Alive
Content-Encoding: gzip
Content-Language: zh-CN
Content-Length: 14403
Content-Type: text/html
Date: Sun, 27 May 2018 15:38:25 GMT
ETag: "e6ec-56d3032d70a32-gzip"
Expires: Sun, 27 May 2018 15:48:25 GMT
Keep-Alive: timeout=5, max=100
Last-Modified: Sun, 27 May 2018 13:44:22 GMT
Server: VWebServer
Vary: User-Agent,Accept-Encoding
X-Frame-Options: SAMEORIGIN
BuiltWith(python自带的模块): 识别网站使用的技术
>>>
>>> import builtwith
>>> builtwith.parse('http://www.bootcss.com/')
java基础417讲解
{'web-servers': ['Nginx'], 'font-scripts': ['Font Awesome'], 'javascript-frameworks': ['Lo-dash', 'Underscore.js', 'Vue.js', 'Zepto', 'jQuery'], 'web-frameworks': ['Twitter Bootstrap']}
>>>
>>> import ssl
>>> ssl._create_default_https_context = ssl._create_unverified_context
>>> builtwith.parse('https://www.jianshu.com/')
{'web-servers': ['Tengine'], 'web-frameworks': ['Twitter Bootstrap', 'Ruby on Rails'], 'programming-languages': ['Ruby']}
python-whois(python自带的模块): 查询网站的所有者

robotparser: 解析robots.txt的工具
>>> from urllib import robotparser>>> parser = robotparser.RobotFileParser()>>> parser.set_url('https://www.taobao.com/robots.txt')>>> parser.read()>>> parser.can_fetch('Hellokitty', 'http://www.taobao.com/article')False>>> parser.can_fetch('Baiduspider', 'http://www.taobao.com/article')True>>> parser.can_fetch('Baiduspider', 'http://www.taobao.com/product')False
一个简单的爬虫
一个基本的爬虫通常分为数据采集(网页下载), 数据处理(网页解析)和数据存储(将有用的信息持久化) 三个部分的内容, 当然更为高级的爬虫在数据采集和处理时会使用并发编程或分布式技术, 其中可能还包括调度器和后台管理程序(监控爬虫的工作状态以及检查数据爬取的结果)

设定抓取目标(种子页面)并获取网页.
当服务器无法访问时, 设置重试次数.
在需要的时候设置用户代理(否则无法访问页面)
对获取的页面进行必要的解码操作
通过正则表达式获取页面中的链接
对链接进行进一步的处理(获取页面并重复上面的操作)
将有用的信息进行持久化(以备后续的处理)
设置包装类进行重试次数
class Retry(object):def __init__(self, *, retry_times=3, wait_secs=5, errors=(Exception,)):
self.retry_times = retry_times
self.wait_secs = wait_secs
self.errors = errorsdef __call__(self, fn):def wrapper(*args, kwargs):try:return fn(*args, kwargs)except self.errors as e:
logging.error(e)
sleep((random() + 1) * self.wait_secs)return wrapperclass Spider(object):def __init__(self):
self.status = SpiderStatus.IDEL@Retry()def fetch(self, current_url, *, user_agent=None, proxies=None, charsets=('utf-8',)):
thread_name = current_thread().name
print(f'[{thread_name}:{current_url}]')
headers = {'user-agent':user_agent} if user_agent else {}
resp = requests.get(current_url, headers=headers, proxies=proxies)return decode_page(resp.content, charsets=charsets) if resp.status_code == 200 else None
3 设置代理,隐藏真实身份
# 实例1: def get_html(url): # 这里我们给url请求,伪装了一个请求头,表示我是一个浏览器(也可以伪装成移动端)
resp = requests.get(url,headers={ 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1'}) return resp #实例2: def get_html(url): # 这里我们的url请求,伪装成百度的爬虫
resp = requests.get(url,headers={ 'user-agent': 'Baiduspider'}) return resp #实例3: def main():
这里我使用了代理的IP,隐藏了自己的真实ip
headers = {'user-agent': 'Baiduspider'} # 隐藏自己的身份IP
proxies = { # 代理IP 'http': '36.22.76.233:35390', 'http': '61.135.217.7:80'
}
base_url = 'https://www.zhihu.com'
seed_url = urljoin(base_url, 'explore')
resp = requests.get(seed_url, headers=headers, proxies=proxies)
常见的伪装 'user-agent’:
1.Android
* Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19
* Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) * * * AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30
* Mozilla/5.0 (Linux; U; Android 2.2; en-gb; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1
2.Firefox
* Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/ Firefox/21.0
* Mozilla/5.0 (Android; Mobile; rv:14.0) Gecko/14.0 Firefox/14.0
3.Google Chrome
* Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
* Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19
4.iOS
* Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3
* Mozilla/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3A101a Safari/419.3
4 - 6解码/正则匹配内容/bs4抓取数据/持久化存储
from urllib.error import URLErrorfrom urllib.request import urlopenimport reimport pymysqlimport sslfrom pymysql import Error# 通过指定的字符集对页面进行解码(不是每个网站都将字符集设置为utf-8)def decode_page(page_bytes, charsets=('utf-8',)):
page_html = Nonefor charset in charsets:try:
page_html = page_bytes.decode(charset)breakexcept UnicodeDecodeError:pass# logging.error('Decode:', error)return page_html# 获取页面的HTML代码(通过递归实现指定次数的重试操作)def get_page_html(seed_url, *, retry_times=3, charsets=('utf-8',)):
page_html = Nonetry:
page_html = decode_page(urlopen(seed_url).read(), charsets)except URLError:# logging.error('URL:', error)if retry_times > 0:return get_page_html(seed_url, retry_times=retry_times - 1,
charsets=charsets)return page_html# 从页面中提取需要的部分(通常是链接也可以通过正则表达式进行指定)def get_matched_parts(page_html, pattern_str, pattern_ignore_case=re.I):
pattern_regex = re.compile(pattern_str, pattern_ignore_case)return pattern_regex.findall(page_html) if page_html else []# 开始执行爬虫程序并对指定的数据进行持久化操作def start_crawl(seed_url, match_pattern, *, max_depth=-1):
conn = pymysql.connect(host='localhost', port=3306,
database='crawler', user='root',
password='', charset='utf8')try:with conn.cursor() as cursor:
url_list = [seed_url]# 通过下面的字典避免重复抓取并控制抓取深度
visited_url_list = {seed_url: 0}while url_list:
current_url = url_list.pop(0)
depth = visited_url_list[current_url]if depth != max_depth:
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk', 'gb2312'))
links_list = get_matched_parts(page_html, match_pattern)
param_list = []for link in links_list:if link not in visited_url_list:
visited_url_list[link] = depth + 1
page_html = get_page_html(link, charsets=('utf-8', 'gbk', 'gb2312'))
headings = get_matched_parts(page_html, r'<h1>(.*)<span')if headings:
param_list.append((headings[0], link))
cursor.executemany('insert into tb_result values (default, %s, %s)',
param_list)
conn.commit()except Error:pass# logging.error('SQL:', error)finally:
conn.close()def main():
ssl._create_default_https_context = ssl._create_unverified_context
start_crawl('http://sports.sohu.com/nba_a.shtml',r'<a[^>]+test=as[^>]*href=["'](.*?)["']',
max_depth=2)if __name__ == '__main__':
main()
注意事项:
1. 处理相对链接. 有的时候我们从页面中获取的链接不是一个完整的绝对链接而是一个相对链接. 在这种情况下需要将其与URL前缀进行拼接(urllib,parse中的urljoin函数可以完成此项操作).
2. 设置dialing服务. 有些网站会限制访问的区域(例如美国的Netfix屏蔽了很多国家的访问), 有些爬虫需要隐藏自己的身份, 在这种情况下可以设置代理服务器(urllib.request中的ProxyHandler就是用来进行此项操作)
3. 限制下载速度. 如果我们的爬虫获取页面的速度过快, 可能就会面临被封禁或者产生’损害动产’的风险(这个可能会导致吃官司且败诉), 可以在两次下载之间添加延时从而对爬虫进行限速.
4. 避免爬虫陷阱. 有些网站会动态生成页面内容, 这回导致产生无限多的页面(例如在线万年历等), 可以通过记录到达当前页面经过了多少个链接(链接深度) 来解决该问题, 当达到实现设定的最大深度时爬虫就不再想队列中添加该网页中的链接了.
5. SSL相关问题, 在使用urlopen打开一个HTPPS连接时会验证一次SSL整数, 如果不作出处理会产生错误提示”SSL:CERTIFICATE_VERIFY_FAILED”, 可以通过以下两种方式进行解决:
使用未经验证的上下文
import ssl
request = urllib.request.Request(url='...', headers={...})
context = ssl._create_unverified_context()
web_page = urllib.request.urlopen(request, context=context
设置全局的取消证书验证
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
Python是什么
Python是著名的“龟叔”Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言。
创始人Guido van Rossum是BBC出品英剧Monty Python’s Flying Circus(中文:蒙提·派森的飞行马戏团)的狂热粉丝,因而将自己创造的这门编程语言命名为Python。
人生苦短,我用python,翻译自"Life is short, you need Python"
Python英式发音:/ˈpaɪθən/ ,中文类似'拍森’。而美式发音:/ˈpaɪθɑːn/,中文类似'拍赏’。我看麻省理工授课教授读的是'拍赏’,我觉得国内大多是读'拍森’吧。
2017年python排第一也无可争议,比较AI第一语言,在当下人工智能大数据大火的情况下,python无愧第一语言的称号,至于C、C++、java都是万年的老大哥了,在代码量比较方面,小编相信java肯定是完爆其它语言的。
不过从这一年的编程语言流行趋势看,java依然是传播最多的,比较无论app、web、云计算都离不开,而其相对python而言,学习路径更困难一点,想要转行编程,而且追赶潮流,python已然是**语言。
许多大型网站就是用Python开发的,国内:豆瓣、搜狐、金山、腾讯、盛大、网易、百度、阿里、淘宝、热酷、土豆、新浪、果壳…; 国外:谷歌、NASA、YouTube、Facebook、工业光魔、红帽…
Python将被纳入高考内容
浙江省信息技术课程改革方案已经出台,Python确定进入浙江省信息技术高考,从2018年起浙江省信息技术教材编程语言将会从vb更换为Python。其实不止浙江,教育大省北京和山东也确定要把Python编程基础纳入信息技术课程和高考的内容体系,Python语言课程化也将成为孩子学习的一种趋势。尤其山东省最新出版的小学信息技术六年级教材也加入了Python内容,小学生都开始接触Python语言了!!
再不学习,又要被小学生完爆了。。。

Python入门教程
Python教程 - 廖雪峰的官方网站
Python官网
Python 100例 | 菜鸟教程
Python中文社区
微信公众号:裸睡的猪
微信跳一跳的python外挂项目:https://github.com/wangshub/wechat_jump_game
微信机器人、爬取京东充气娃娃、豆瓣影评、新浪话题、模拟登录淘宝,爬取淘宝案例,都在vx公众号「裸睡的猪」
500G 百度云Python入门资料免费送,公众号回复:python入门
Python能做什么
网络爬虫
Web应用开发
系统网络运维
科学与数字计算
图形界面开发
网络编程
自然语言处理(NLP)
人工智能
区块链
多不胜举。。。
Python入门爬虫
这是我的第一个python项目,在这里与大家分享出来~
需求
我们目前正在开发一款产品其功能大致是:用户收到短信如:购买了电影票或者火车票机票之类的事件。然后app读取短信,解析短信,获取时间地点,然后后台自动建立一个备忘录,在事件开始前1小时提醒用户。
设计
开始我们将解析的功能放在了服务端,但是后来考虑到用户隐私问题。后来将解析功能放到了app端,服务端只负责收集数据,然后将新数据发送给app端。
关于服务端主要是分离出两个功能,一、响应app端请求返回数据。二、爬取数据,存入数据库。
响应请求返回数据使用java来做,而爬取数据存入数据库使用python来做,这样分别使用不同语言来做是因为这两种语言各有优势,java效率比python高些,适合做web端,而爬取数据并不是太追求性能且python语言和大量的库适合做爬虫。
代码
本项目使用python3的版本
了解这个项目你只需要有简单的python基础,能了解python语法就可以。其实我自己也是python没学完,然后就开始写,遇到问题就百度,边做边学这样才不至于很枯燥,因为python可以做一些很有意思的事情,比如模拟连续登录挣积分,比如我最近在写一个预定模范出行车子的python脚本。推荐看廖雪峰的python入门教程
首先带大家看看我的目录结构,开始我打算是定义一个非常好非常全的规范,后来才发现由于自己不熟悉框架,而是刚入门级别,所以就放弃了。从简而入:

下面咱们按照上图中的顺序,从上往下一个一个文件的讲解init.py包的标识文件,python包就是文件夹,当改文件夹下有一个init.py文件后它就成为一个package,我在这个包中引入一些py供其他py调用。
init.py
# -*- coding: UTF-8 -*-
# import need manager module
import MongoUtil
import FileUtil
import conf_dev
import conf_test
import scratch_airport_name
import scratch_flight_number
import scratch_movie_name
import scratch_train_number
import scratch_train_station
import MainUtil
下面两个是配置文件,第一个是开发环境的(windows),第二个是测试环境的(linux),然后再根据不同系统启用不同的配置文件
conf_dev.py
# -*- coding: UTF-8 -*-
# the configuration file of develop environment
# path configure
data_root_path = 'E:/APK98_GNBJ_SMARTSERVER/Proj-gionee-data/smart/data' # mongodb configure
user = "cmc" pwd = "" server = "localhost" port = "27017" db_name = "smartdb"
conf_test.py
# -*- coding: UTF-8 -*-
# the configuration file of test environment
#path configure
data_root_path = '/data/app/smart/data' #mongodb configure
user = "smart" pwd = "" server = "10.8.0.30" port = "27017" db_name = "smartdb"
下面文件是一个util文件,主要是读取原文件的内容,还有将新内容写入原文件。
FileUtil.py
# -*- coding: UTF-8 -*-
import conf_dev
import conf_test
import platform
# configure Multi-confronment
# 判断当前系统,并引入相对的配置文件
platform_os = platform.system()
config = conf_dev
if (platform_os == 'Linux'):
config = conf_test
# path
data_root_path = config.data_root_path
# load old data
def read(resources_file_path, encode='utf-8'):
file_path = data_root_path + resources_file_path
outputs = []
for line in open(file_path, encoding=encode):
if not line.startswith("//"):
outputs.append(line.strip(' ').split(',')[-1])
return outputs
# append new data to file from scratch
def append(resources_file_path, data, encode='utf-8'):
file_path = data_root_path + resources_file_path
with open(file_path, 'a', encoding=encode) as f:
f.write(data)
f.close
下面这个main方法控制着执行流程,其他的执行方法调用这个main方法
MainUtil.py
# -*- coding: UTF-8 -*-
import sys
from datetime import datetime
import MongoUtil
import FileUtil
# @param resources_file_path 资源文件的path
# @param base_url 爬取的连接
# @param scratch_func 爬取的方法
def main(resources_file_path, base_url, scratch_func):
old_data = FileUtil.read(resources_file_path) #读取原资源
new_data = scratch_func(base_url, old_data) #爬取新资源
if new_data:#如果新数据不为空
date_new_data = "//" + datetime.now().strftime('%Y-%m-%d') + " " + " ".join(new_data) + " " #在新数据前面加上当前日期
FileUtil.append(resources_file_path, date_new_data) #将新数据追加到文件中
MongoUtil.insert(resources_file_path, date_new_data)#将新数据插入到mongodb数据库中
else: #如果新数据为空,则打印日志
print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '----', getattr(scratch_func, '__name__'), ": nothing to update ")
将更新的内容插入mongodb中
MongoUtil.py
# -*- coding: UTF-8 -*-
import platform
from pymongo import MongoClient
from datetime import datetime, timedelta, timezone
import conf_dev
import conf_test
# configure Multi-confronment
platform_os = platform.system()
config = conf_dev
if (platform_os == 'Linux'):
config = conf_test
# mongodb
uri = 'mongodb://' + config.user + ':' + config.pwd + '@' + config.server + ':' + config.port + '/' + config.db_name
# 将数据写入mongodb
# @author chenmc
# @param uri connect to mongodb
# @path save mongodb field
# @data save mongodb field
# @operation save mongodb field default value 'append' # @date 2017/12/07 16:30 # 先在mongodb中插入一条自增数据 db.sequence.insert({ "_id" : "version","seq" : 1})
def insert(path, data, operation='append'):
client = MongoClient(uri)
resources = client.smartdb.resources
sequence = client.smartdb.sequence
seq = sequence.find_one({"_id": "version"})["seq"] #获取自增id
sequence.update_one({"_id": "version"}, {"$inc": {"seq": 1}}) #自增id+1
post_data = {"_class": "com.gionee.smart.domain.entity.Resources", "version": seq, "path": path,
"content": data, "status": "enable", "operation": operation,
"createtime": datetime.now(timezone(timedelta(hours=8)))}
resources.insert(post_data) #插入数据
项目引入的第三方库,可使用pip install -r requirements.txt下载第三方库
requirements.txt
# need to install module# need to install module
bs4
pymongo
requests
json
下面真正的执行方法来了,这五个py分别表示爬取五种信息:机场名、航班号、电影名、列车号、列车站。他们的结构都差不多,如下:
第一部分:定义查找的url;
第二部分:获取并与旧数据比较,返回新数据;
第三部分:main方法,执行写入新数据到文件和mongodb中;
scratch_airport_name.py:爬取全国机场
# -*- coding: UTF-8 -*-
import requests
import bs4
import json
import MainUtil
resources_file_path = '/resources/airplane/airportNameList.ini' scratch_url_old = 'https://data.variflight.com/profiles/profilesapi/search' scratch_url = 'https://data.variflight.com/analytics/codeapi/initialList' get_city_url = 'https://data.variflight.com/profiles/Airports/%s' #传入查找网页的url和旧数据,然后本方法会比对原数据中是否有新的条目,如果有则不加入,如果没有则重新加入,最后返回新数据
def scratch_airport_name(scratch_url, old_airports):
new_airports = []
data = requests.get(scratch_url).text
all_airport_json = json.loads(data)['data']
for airport_by_word in all_airport_json.values():
for airport in airport_by_word:
if airport['fn'] not in old_airports:
get_city_uri = get_city_url % airport['id']
data2 = requests.get(get_city_uri).text
soup = bs4.BeautifulSoup(data2, "html.parser")
city = soup.find('span', text="城市").next_sibling.text
new_airports.append(city + ',' + airport['fn'])
return new_airports
#main方法,执行这个py,默认调用main方法,相当于java的mainif __name__ == '__main__':
MainUtil.main(resources_file_path, scratch_url, scratch_airport_name)
scratch_flight_number.py:爬取全国航班号
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import requests
import bs4
import MainUtil
resources_file_path = '/resources/airplane/flightNameList.ini' scratch_url = 'http://www.variflight.com/sitemap.html?AE71649A58c77=' def scratch_flight_number(scratch_url, old_flights):
new_flights = []
data = requests.get(scratch_url).text
soup = bs4.BeautifulSoup(data, "html.parser")
a_flights = soup.find('div', class_='list').find_all('a', recursive=False)
for flight in a_flights:
if flight.text not in old_flights and flight.text != '国内航段列表':
new_flights.append(flight.text)
return new_flights
if __name__ == '__main__':
MainUtil.main(resources_file_path, scratch_url, scratch_flight_number)
scratch_movie_name.py:爬取最近上映的电影
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import re
import requests
import bs4
import json
import MainUtil
# 相对路径,也是需要将此路径存入数据库
resources_file_path = '/resources/movie/cinemaNameList.ini' scratch_url = 'http://theater.mtime.com/China_Beijing/' # scratch data with define url
def scratch_latest_movies(scratch_url, old_movies):
data = requests.get(scratch_url).text
soup = bs4.BeautifulSoup(data, "html.parser")
new_movies = []
new_movies_json = json.loads(
soup.find('script', text=re.compile("var hotplaySvList")).text.split("=")[1].replace(";", ""))
coming_movies_data = soup.find_all('li', class_='i_wantmovie')
# 上映的电影
for movie in new_movies_json:
move_name = movie['Title']
if move_name not in old_movies:
new_movies.append(movie['Title'])
# 即将上映的电影
for coming_movie in coming_movies_data:
coming_movie_name = coming_movie.h3.a.text
if coming_movie_name not in old_movies and coming_movie_name not in new_movies:
new_movies.append(coming_movie_name)
return new_movies
if __name__ == '__main__':
MainUtil.main(resources_file_path, scratch_url, scratch_latest_movies)
scratch_train_number.py:爬取全国列车号
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import requests
import bs4
import json
import MainUtil
resources_file_path = '/resources/train/trainNameList.ini' scratch_url = 'http://www.59178.com/checi/' def scratch_train_number(scratch_url, old_trains):
new_trains = []
resp = requests.get(scratch_url)
data = resp.text.encode(resp.encoding).decode('gb2312')
soup = bs4.BeautifulSoup(data, "html.parser")
a_trains = soup.find('table').find_all('a')
for train in a_trains:
if train.text not in old_trains and train.text:
new_trains.append(train.text)
return new_trains
if __name__ == '__main__':
MainUtil.main(resources_file_path, scratch_url, scratch_train_number)
scratch_train_station.py:爬取全国列车站
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import requests
import bs4
import random
import MainUtil
resources_file_path = '/resources/train/trainStationNameList.ini' scratch_url = 'http://www.smskb.com/train/' def scratch_train_station(scratch_url, old_stations):
new_stations = []
provinces_eng = (
"Anhui", "Beijing", "Chongqing", "Fujian", "Gansu", "Guangdong", "Guangxi", "Guizhou", "Hainan", "Hebei",
"Heilongjiang", "Henan", "Hubei", "Hunan", "Jiangsu", "Jiangxi", "Jilin", "Liaoning", "Ningxia", "Qinghai",
"Shandong", "Shanghai", "Shanxi", "Shanxisheng", "Sichuan", "Tianjin", "Neimenggu", "Xianggang", "Xinjiang",
"Xizang",
"Yunnan", "Zhejiang")
provinces_chi = (
"安徽", "北京", "重庆", "福建", "甘肃", "广东", "广西", "贵州", "海南", "河北",
"黑龙江", "河南", "湖北", "湖南", "江苏", "江西", "吉林", "辽宁", "宁夏", "青海",
"山东", "上海", "陕西", "山西", "四川", "天津", "内蒙古", "香港", "新疆", "西藏",
"云南", "浙江")
for i in range(0, provinces_eng.__len__(), 1):
cur_url = scratch_url + provinces_eng[i] + ".htm"
resp = requests.get(cur_url)
data = resp.text.encode(resp.encoding).decode('gbk')
soup = bs4.BeautifulSoup(data, "html.parser")
a_stations = soup.find('left').find('table').find_all('a')
for station in a_stations:
if station.text not in old_stations:
new_stations.append(provinces_chi[i] + ',' + station.text)
return new_stations
if __name__ == '__main__':
MainUtil.main(resources_file_path, scratch_url, scratch_train_station)
将项目放到测试服务器(centos7系统)中运行起来,我写了一个crontab,定时调用他们,下面贴出crontab。
/etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# For details see man 4 crontabs
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
0 0 * * * root python3 /data/app/smart/py/scratch_movie_name.py>> /data/logs/smartpy/out.log 2>&1
0 1 * * 1 root python3 /data/app/smart/py/scratch_train_station.py >> /data/logs/smartpy/out.log 2>&1
0 2 * * 2 root python3 /data/app/smart/py/scratch_train_number.py >> /data/logs/smartpy/out.log 2>&1
0 3 * * 4 root python3 /data/app/smart/py/scratch_flight_number.py >> /data/logs/smartpy/out.log 2>&1
0 4 * * 5 root python3 /data/app/smart/py/scratch_airport_name.py >> /data/logs/smartpy/out.log 2>&1
后续
目前项目已经正常运行了三个多月啦。。。

有问题反馈
在阅读与学习中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流
微信公众号:裸睡的猪
在下面留言
直接给我私信
关于此公众号
后期或提供各种软件的免费激活码
推送python,java等编程技术文章和面试技巧
当然你们可以将你们感兴趣的东西直接送给我
谢谢你们真诚的关注,此公众号以后获得的收益将通过抽奖的形式送给大家
以后如果博主要创业的话,也会在此公众号中挑选小伙伴哦~
希望大家分享出去,让更多想学习python的朋友看到~
我的第一个Python爬虫——谈心得
2018年3月27日,继开学以来,开了软件工程和信息系统设计,想来想去也没什么好的题目,干脆就想弄一个实用点的,于是产生了做“学生服务系统”想法。相信各大高校应该都有本校APP或超级课程表之类的软件,在信息化的时代能快速收集/查询自己想要的咨询也是种很重要的能力,所以记下了这篇博客,用于总结我所学到的东西,以及用于记录我的第一个爬虫的初生。
文章目录
一、做爬虫所需要的基础
二、介绍几款优秀制作爬虫的辅助工具
三、最简单的爬虫试例
四、需要模拟登录后再爬取的爬虫所需要的信息
4.1.登录分析
4.2信息提取
五、开始编码爬虫
六、爬虫技术的拓展与提高
七、后记
一、做爬虫所需要的基础
要做一只爬虫,首先就得知道他会干些什么,是怎样工作的。所以得有一些关于HTML的前置知识,这一点做过网页的应该最清楚了。
HTML(超文本标记语言),是一种标记性语言,本身就是一长串字符串,利用各种类似 < a >,< /a>这样的标签来识别内容,然后通过浏览器的实现标准来翻译成精彩的页面。当然,一个好看的网页并不仅仅只有HTML,毕竟字符串是静态的,只能实现静态效果,要作出漂亮的网页还需要能美化样式的CSS和实现动态效果的JavaScipt,只要是浏览器都是支持这些玩意儿的。
嗯,我们做爬虫不需要了解太多,只需要了解HTML是基于文档对象模型(DOM)的,以树的结构,存储各种标记,就像这样:

之后会用到这种思想来在一大堆HTML字符串中找出我们想要的东西。
了解了这个然后还得了解网页和服务器之间是怎么通信的,这就得稍微了解点HTTP协议,基于TCP/IP的应用层协议,规定了浏览器和服务器之间的通信规则,简单粗暴的介绍几点和爬虫相关的就是:
浏览器和服务器之间有如下几种通信方式:
GET:向服务器请求资源,请求以明文的方式传输,一般就在URL上能看到请求的参数
POST:从网页上提交表单,以报文的形式传输,请求资源
还有几种比较少见就不介绍了。
了解了这两点就可以准备工具了,当然,对爬虫有兴趣还可以了解一下爬虫的发展史。
二、介绍几款优秀制作爬虫的辅助工具
由于我是采用python3.6开发的,然后从上文的介绍中,也该知道了一只爬虫是需要从HTML中提取内容,以及需要和网页做交互等。
如果不采用爬虫框架的话,我建议采用:
BeautifulSoup 库 ,一款优秀的HTML/XML解析库,采用来做爬虫,
不用考虑编码,还有中日韩文的文档,其社区活跃度之高,可见一斑。
[注] 这个在解析的时候需要一个解析器,在文档中可以看到,推荐lxml
Requests 库,一款比较好用的HTTP库,当然python自带有urllib以及urllib2等库,
但用起来是绝对没有这款舒服的,哈哈
Fiddler. 工具,这是一个HTTP抓包软件,能够截获所有的HTTP通讯。
如果爬虫运行不了,可以从这里寻找答案,官方链接可能进不去,可以直接百度下载
爬虫的辅助开发工具还有很多,比如Postman等,这里只用到了这三个,相信有了这些能减少不少开发阻碍。
三、最简单的爬虫试例
最简单的爬虫莫过于单线程的静态页面了,这甚至都不能叫爬虫,单单一句正则表达式即可匹配出所有内容,比如各种榜单:豆瓣电影排行榜,这类网站爬取规则变化比较少,用浏览器自带的F12的审查很容易找到需要爬取信息的特征:

见到花花绿绿的HTML代码不要害怕,一个一个点,直到找到需要的信息就行了,可以看到所有电影名都是在这样
<div class = "pl2">
之下的,每有一个这样的标签就代表一个电影,从他的孩子< span >中即可抓取到电影名。
代码如下:
from bs4 import BeautifulSoup
from lxml import html
import xml
import requests
url = "https://movie.douban.com/chart"
f = requests.get(url) #Get该网页从而获取该html内容
soup = BeautifulSoup(f.content, "lxml") #用lxml解析器解析该网页的内容, 好像f.text也是返回的html
#print(f.content.decode())#尝试打印出网页内容,看是否获取成功
#content = soup.find_all('div',class_="p12" ) #尝试获取节点,因为calss和关键字冲突,所以改名class_
for k in soup.find_all('div',class_='pl2'):#,找到div并且class为pl2的标签
a = k.find_all('span') #在每个对应div标签下找span标签,会发现,一个a里面有四组span
print(a[0].string)#取第一组的span中的字符串
抓取结果如下:

乍一看,就这么个玩意儿,这些电影名还不如直接自己去网页看,这有什么用呢?但是,你想想,只要你掌握了这种方法,如果有翻页你可以按照规则爬完了一页就解析另外一页HTML(通常翻页的时候URL会规律变化,也就是GET请求实现的翻页),也就是说,只要掌握的爬取方法,无论工作量有多么大都可以按你的心思去收集想要的数据了。
四、需要模拟登录后再爬取的爬虫所需要的信息
4.1.登录分析
刚才的爬虫未免太简单,一般也不会涉及到反爬虫方面,这一次分析需要登录的页面信息的爬取,按照往例,首先打开一个网页:
我选择了我学校信息服务的网站,登录地方的代码如下:

可以看到验证码都没有,就只有账号密码以及提交。光靠猜的当然是不行的,一般输入密码的地方都是POST请求。
POST请求的响应流程就是 客户在网页上填上服务器准备好的表单并且提交,然后服务器处理表单做出回应。一般就是用户填写账号、密码、验证码然后把这份表单提交给服务器,服务器从数据库进行验证,然后作出不同的反应。在这份POST表单中可能还有一些不需要用户填写的用脚本生成的隐藏属性作为反爬虫的手段。
要知道表单格式可以先试着随便登录一次,然后在F12中的network中查看登录结果,如图:

图1

图2
【注】如果用真正的账号密码登录,要记住勾选上面的Preserve log,这样即使网页发生了跳转之前的信息也还在。
从上面的两张图中很容易发现其中的一个POST请求, login?serv…就是登录请求了
可以看到这个登录请求所携带的信息有:
General: 记录了请求方式,请求地址,以及服务器返回的状态号 200等
Response Headers: 响应头,HTTP响应后传输的头部消息
Request Headers: 请求头,重点!!,向服务器发送请求时,发出的头部消息,之中很多参数都是爬虫需要模拟出来传送给服务器的。
From Data:表单,重点!!,在这里表单中有:
username: 12345
password: MTIzNDU=
lt: e1s1
_eventId: submit
我明明都填的12345,为什么密码变了呢?可以看出这密码不是原始值,应该是编码后的产物,网站常用的几种编码/加密方法就几种,这里是采用的base64编码,如果对密码编码的方式没有头绪可以仔细看看登录前后页面的前端脚本。运气好可以看到encode函数什么的。
4.2信息提取
如果了解过Resquests库的文档就知道,发送一个一般的POST请求所需要的参数构造是这样的:
r = requests.post(url,[data],[header],[json],[kwargs])
/*
url -- URL for the new Request object.
data -- (optional) Dictionary, bytes, or file-like object to send in the body of the Request.
json -- (optional) json to send in the body of the Request.
kwargs -- Optional arguments that request takes.
*/
从上面的两张图片中即可找到发送一个正确的请求所需要的参数,即 url 和 data :
url 即上面的 Request URL:
Request URL: http://uia.hnist.cn/sso/login?service=http%3A%2F%2Fportal.hnist.cn%2Fuser%2FsimpleSSOLogin
data 即上面的From data:
username: 12345
password: MTIzNDU=
lt: e1s1
_eventId: submit
收集到了必要的信息还得了解三点:
一、登录后的网页和服务器建立了联系,所以能和服务器进行通信,但即使你从这个网页点击里面的超链接跳转到另外一个子网页,在新网页中还是保持登录状态的在不断的跳转中是怎么识别用户的呢?
在这里,服务器端一般是采用的Cookie技术,登陆后给你一个Cookie,以后你发出跳转网页的请求就携带该Cookie,服务器就能知道是你在哪以什么状态点击的该页面,也就解决了HTTP传输的无状态问题。
很明显,在模拟登录以后保持登录状态需要用得着这个Cookie,当然Cookie在请求头中是可见的,为了自己的账号安全,请不要轻易暴露/泄漏自己的Cookie
二、先了解一下,用python程序访问网页的请求头的User-Agent是什么样的呢?没错,如下图所示,很容易分辨这是程序的访问,也就是服务器知道这个请求是爬虫访问的结果,如果服务器做了反爬虫措施程序就会访问失败,所以需要程序模拟浏览器头,让对方服务器认为你是使用某种浏览器去访问他们的。

三、查找表单隐藏参数的获取方式,在上文表单列表中有个lt参数,虽然我也不知道他是干嘛的,但通过POST传输过去的表单肯定是会经过服务器验证的,所以需要弄到这份参数,而这份参数一般都会在HTML页面中由JS脚本自动生成,可以由Beautifulsoup自动解析抓取。
关于Fiddler的使用和请求信息相关信息可以查看链接:https://zhuanlan.zhihu.com/p/?refer=xmucpp
嗯,最重要的几样东西已经收集完毕,对Cookie和请求头的作用也有了个大概的了解,然后开始发送请求试试吧~
五、开始编码爬虫
如果用urllib库发送请求,则需要自己编码Cookie这一块(虽然也只要几行代码),但用Requests库就不需要这样,在目前最新版本中,requests.Session提供了自己管理Cookie的持久性以及一系列配置,可以省事不少。
先以面对过程的方式实验地去编码:
from bs4 import BeautifulSoup
from lxml import html
import requests
# 在这先准备好请求头,需要爬的URL,表单参数生成函数,以及建立会话
# 1
header={
"Accept": "text/html, application/xhtml+xml, image/jxr, */*",
"Referer": "http://uia.hnist.cn/sso/login?service=http%3A%2F%2Fportal.hnist.
cn%2Fuser%2FsimpleSSOLogin",
"Accept-Language": "zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type": "application/x-www-form-urlencoded",
"Accept-Encoding": "gzip, deflate",
"Connection": "Keep-Alive",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
"Accept-Encoding": "gzip, deflate",
"Origin": "http://uia.hnist.cn",
"Upgrade-Insecure-Requests": "1",
#Cookie由Session管理,这里不用传递过去,千万不要乱改头,我因为改了头的HOST坑了我两天
}
School_login_url = 'http://uia.hnist.cn/sso/login?
service=http%3A%2F%2Fportal.hnist.cn%2Fuser%2FsimpleSSOLogin'#学校登录的URL
page = requests.Session() #用Session发出请求能自动处理Cookie等问题
page.headers = header #为所有请求设置头
page.get(School_login_url)#Get该地址建立连接(通常GET该网址后,服务器会发送一些用于
验证的参数用于识别用户,这些参数在这就全由requests.Session处理了)
def Get_lt():#获取参数 lt 的函数
f = requests.get(School_login_url,headers = header)
soup = BeautifulSoup(f.content, "lxml")
once = soup.find('input', {'name': 'lt'})['value']
return once
lt = Get_lt() #获取lt
From_Data = { #表单
'username': 'your username',
'password': 'Base64 encoded password',
#之前说过密码是通过base64加密过的,这里得输入加密后的值,或者像lt一样写个函数
'lt': lt,
'_eventId': 'submit',
}
# 1 end #
#
# 在这一段向登录网站发送POST请求,并判断是否成功返回正确的内容
# 2
q = page.post(School_login_url,data=From_Data,headers=header)
#发送登陆请求
查看POST请求状态
#print(q.url)#这句可以查看请求的URL
#print(q.status_code) #这句可以查看请求状态
#for (i,j) in q.headers.items():
#print(i,':',j)#这里可以查看响应头
#print(' ')
#for (i,j) in q.request.headers.items():
#print(i,':',j)#这里可以查看请求头
上面的内容用于判断爬取情况,也可以用fiddle抓包查看
f = page.get('http://uia.hnist.cn')#GET需要登录后(携带cookie)才能查看的网站
print("body:",f.text)
# 进入查成绩网站,找到地址,请求并接收内容
proxies = { #代理地址,这里代理被注释了,对后面没影响,这里也不需要使用代理....
#"http": "http://x.x.x.x:x",
#"https": "http://x.x.x.x:x",
}
# 查成绩网站的text格式表单,其中我省略了很多...
str = """callCount=1
httpSessionId=DA0080E0317A1AD0FDD3E09E095CB4B7.portal254
scriptSessionId=D7E8882CB2F7AB18F62EED380
page=/web/guest/788
"""
这是由于该服务器关于表单提交部分设计比较垃圾,所以不用去在意表单内容含义
f = page.post('http://portal.hnist.cn/portal_bg_ext/dwr/plainjs/
ShowTableAction.showContent.dwr',data=str,proxies=proxies)
#查成绩的地址,表单参数为上面的str
查看地址,返回状态,以及原始内容"""
print("f:",f.url)
print(f.status_code)
text = f.content.decode('unicode_escape')
print(text.encode().decode()) #因为原始内容中有됻式的编码,所以使用这句解码
#"""
2 end
# 解析获得的内容,并清洗数据,格式化输出...
# 3 #
[注] 如果使用了Fiddler,他会自动为Web的访问设置一个代理,这时候如果你关闭了Fiddler可能爬虫会无法正常工作,这时候你选择浏览器直连,或者设置爬虫的代理为Fiddler即可。
[注2]爬虫不要频率太快,不要影响到别人服务器的正常运行,如果不小心IP被封了可以使用代理(重要数据不要使用不安全的代理),网上有很多收费/免费的代理,可以去试下。
过程中获得的经验:
在上面第一部分,不知道作用的参数不要乱填,只需要填几个最重要的就够了,比如UA,有时候填了不该填的请求将会返回错误状态.,尽量把可分离的逻辑写成函数来调用,比如生成的表单参数,加密方法等.
在上面第二部分如果请求失败可以配合抓包软件查看程序和浏览器发送的请求有什么差别,遗漏了什么重要的地方,尽量让程序模仿浏览器的必要的行为。
第三部分中,因为拿到的数据是如下图1这样的,所以需要最后输出后decode,然后再使用正则表达式提取出双引号中的内容连接诶成一个标记语言的形式,再使用Beautifulsoup解析获得需要的数据,如图2.
中途可能利用的工具有:
官方正则表达式学习网站
HTML格式美化
正则表达式测试

图1

图2
六、爬虫技术的拓展与提高
经历了困难重重,终于得到了想要的数据,对于异步请求,使用JS渲染页面后才展示数据的网页,又或是使用JS代码加密过的网页,如果花时间去分析JS代码来解密,简单的公有的加密方法倒是无所谓,但对于特别难的加密就有点费时费力了,在要保持抓取效率的情况下可以使用能使用Splash框架:
这是一个Javascript渲染服务,它是一个实现了HTTP API的轻量级浏览器,Splash是用Python实现的,同时使用Twisted和QT。Twisted(QT)用来让服务具有异步处理能力,以发挥webkit的并发能力。
就比如像上面返回成绩地址的表单参数,格式为text,并且无规律,有大几十行,如果要弄明白每个参数是什么意思,还不如加载浏览器的JS 或 使用浏览器自动化测试软件来获取HTML了,所以,遇到这种情况,在那么大一段字符串中,只能去猜哪些参数是必要的,哪些参数是不必要的,比如上面的,我就看出两个是有关于返回页面结果的,其余的有可能存在验证身份的,时间的什么的。
对于信息的获取源,如果另外的网站也有同样的数据并且抓取难度更低,那么换个网站爬可能是个更好的办法,以及有的网站根据请求头中的UA会产生不同的布局和处理,比如用手机的UA可能爬取会更加简单。
七、后记
几天后我发现了另一个格式较好的页面,于是去爬那个网站,结果他是.jsp的,采用之前的方法跳转几个302之后就没有后续了…后来才猜想了解到,最后一个302可能是由JS脚本跳转的,而我没有执行JS脚本的环境,也不清楚他执行的哪个脚本,传入了什么参数,于是各种尝试和对比,最后发现:正常请求时,每次都多2个Cookie,开始我想,Cookie不是由Session管理不用去插手的吗?然后我想以正常方式获得该Cookie,请求了N个地址,结果始终得不到想要的Cookie,于是我直接使用添加了两个Cookie,还真成了…神奇…
当然,过了一段时间后,又不行了,于是仔细观察,发现每次就JSESSIONID这一个Cookie对结果有影响,传递不同的值到不同的页面还…虽然我不认同这种猜的,毫无逻辑效率的瞎试。但经历长时间的测试和猜测,对结果进行总结和整理也是能发现其中规律的。
关于判断某动作是不是JS,可以在Internet选项中设置禁止使用JS
关于失败了验证的方法,我强烈建议下载fiddler,利用新建视图,把登录过程中所有的图片,CSS等文件去掉以后放到新视图中,然后利用程序登录的过程也放一个视图当中,如果没有在响应中找到需要的Cookie,还可以在视图中方便的查看各个JS文件,比浏览器自带的F12好用太多了。 如下图:

总之,经过这段时间的尝试,我对爬虫也有了个初步的了解,在这方面,也有了自己做法:
抓包请求 —> 模仿请求头和表单—>如果请求失败,则仔细对比正常访问和程序访问的数据包 —>成功则根据内容结构进行解析—>清清洗数据并展示
python爬虫入门教程
爬虫入门系列教程:
python爬虫入门教程(一):开始爬虫前的准备工作
python爬虫入门教程(二):开始一个简单的爬虫
python爬虫入门教程(三):淘女郎爬虫 ( 接口解析 | 图片下载 )
等待更新…
前言
学习python爬虫也有一段时间了,各种爬虫技术多多少少也接触过一些。因为有学弟学妹说想学爬虫,我萌生了写个简单的python爬虫教程的想法。
一来,给学弟学妹们提供方便,提供一个入门的渠道;二来,总结这些天自身所学,希望可以借此提高自己,加深认识;三来,如果偶尔有大佬路过,求大佬指出文中错误的地方,感激不尽,毕竟我也只是个菜,还菜的那么执着= =
1.什么是网络爬虫
百度百科给的介绍如下:
网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。
简单来说,网络爬虫就是一段程序,它模拟人类访问互联网的形式,不停地从网络上抓取我们需要的数据。我们可以定制各种各样的爬虫,来满足不同的需求,如果法律允许,你可以采集在网页上看到的、任何你想要获得的数据。
爬虫是一种从网络上高速提取数据的方式(当然它也可以用作它途,如果需要的话。因为从本质上来说,它就是利用python与网站进行交互、并对网站返回的结果进行分析和处理的过程)。你可以把爬虫想想成一个机器人(其实它就是个机器人,不过是软件形式上的),坐在一台电脑旁边,不停地点开一个个网页,从里面复制指定文本或图片进行保存(假设需求就是保存指定文本或图片)。神奇的是,它的手速非常非常快,一阵眼花缭乱中,本地磁盘中就已经存了一大堆数据= =
2.网络爬虫能做什么
上面说了一大堆,可能也没说清楚爬虫究竟是什么。没关系,我们举几个例子来看。
比如,学校经常在官网上发布一些比较重要的通知,我不想每天都花费精力去看官网,却又想当有新通知的时候,就能知道,并看到它。
这种时候,就需要爬虫来帮忙咯。写一个程序,让它每半个小时或一个小时就去访问一次官网,检查有没有新的通知,如果没有,就什么都不做,等待下次检查,如果有,就将新通知从网页中提取出来,保存,并发邮件告诉我们通知的内容,然后继续等待即可。
假设,最近有点闲了,想看看电影,但又不想看烂片。于是,默默打开了豆瓣,上面有电影评分嘛,还有影评。我想要获取所以评分在8分以上的电影名称、简介以及该电影的部分热评,从中选出想看的出来。
这个时候,一个小小的爬虫就能轻轻松松地从一堆电影中找出符合要求的保存下来,不用费神地一个个去瞅了。如果你还会自然语言处理和机器学习,那就更棒了,或许你可以直接对这些数据进行分析,让程序匹配出你感兴趣的电影来。(当然了,举例子嘛,现实生活中,显然投入和产出不成正比= =看个电影哪那么麻烦orz)
再比如,采集京东、淘宝的商品评论信息啦,采集招聘网站的企业职位信息啦,采集微博信息啦,或者只是简单地爬一些美女图片啦……各种情况,采什么,看需求吧。
3.开发爬虫的准备工作
3.1 编程语言
做开发嘛,首先,我们要有一门开发语言,这里我选择python。
python是一门非常容易上手的解释型语言,还有大量的第三方类库,使用起来非常方便。编程语言用起来再也不用脑阔疼了,人生苦短,快用python~
写爬虫之前,我们需要了解python的基础语法。更深入的用法可以不用太着急去学,在使用的过程中碰到各种问题,再去学习并解决就可以了。关于python的用法,我就不写了,网上有很多教程。这里推荐一下廖雪峰大大的python教程,分python2.7和python3.5两个版本,我开发使用的是2.7,。
教程链接:Python 2.7教程 Python 3.5教程
3.2 开发平台和环境
首先,不黑什么平台,但还是要说一句,就开发而言,linux比windows用起来舒服得多。Mac没用过,不清楚,就不评论了。
开发平台,推荐linux。我使用的是ubuntu,感觉良好。不要有太大的心里压力,因为现在ubuntu的图形界面做的已经很友好了,linux小白使用起来也没什么问题,真碰到问题再去百度或者谷歌就行了。
不推荐windows的原因之一是,在windows上面,很多类库安装起来会非常麻烦= =而且容易碰到各种问题,相比较而言,ubuntu就省心了很多= =
如果一定要用windows,并且有一台远程的linux主机的话,也可以考虑使用远程的python环境来开发。这一点,如果有时间,我写个教程吧,没时间就算了= =大家可以搜索一下关键词。
IDE的话,推荐使用Pycharm。 windows、linux、macos多平台支持,非常好用,值得拥有。详细的我就不介绍了,用一用就会了。
pycharm的官网链接:Download PyCharm
4.推荐的python爬虫学习书籍
这里推荐两本很不错的python爬虫入门书籍:
1.米切尔 (Ryan Mitchell) (作者), 陶俊杰 (译者), 陈小莉 (译者)的Python网络数据采集
2.崔庆才 (作者)的Python3网络爬虫开发实战
上一篇讲了开始爬虫前的准备工作。当我们完成开发环境的安装、IDE的配置之后,就可以开始开发爬虫了。 这一篇,我们开始写一个超级简单的爬虫。
1.爬虫的过程分析
当人类去访问一个网页时,是如何进行的?
①打开浏览器,输入要访问的网址,发起请求。
②等待服务器返回数据,通过浏览器加载网页。
③从网页中找到自己需要的数据(文本、图片、文件等等)。
④保存自己需要的数据。
对于爬虫,也是类似的。它模仿人类请求网页的过程,但是又稍有不同。
首先,对应于上面的①和②步骤,我们要利用python实现请求一个网页的功能。
其次,对应于上面的③步骤,我们要利用python实现解析请求到的网页的功能。
最后,对于上面的④步骤,我们要利用python实现保存数据的功能。
因为是讲一个简单的爬虫嘛,所以一些其他的复杂操作这里就不说了。下面,针对上面几个功能,逐一进行分析。
2.如何用python请求一个网页
作为一门拥有丰富类库的编程语言,利用python请求网页完全不在话下。这里推荐一个非常好用的第三方类库requests。
2.1 requests
2.1.1 安装方式
打开终端或者cmd,在里面输入以下指令并回车
pip3 install requests

一般不会出什么问题,如果下载太慢,是因为pip使用的源服务器在国外,可以设置pip使用国内镜像源,设置方法可以参考PyPI使用国内源。
2.1.2 测试是否安装成功
在命令行中输入,敲击回车,进入交互环境。在里面输入以下代码并回车:
import requests
如果不报错,就安装成功了,如下图:

2.2 使用requests请求网页
打开pycharm,创建一个项目,嗯,随便取个名字吧。

创建成功后,再创建一个py文件,用来写代码。嗯,再随便取个名字= =教程(二)的2.2,那就吧。

在里面输入以下代码:
#coding=utf-8import requests
resp=requests.get('https://www.baidu.com') #请求百度首页print(resp) #打印请求结果的状态码print(resp.content) #打印请求到的网页源码
对上面的代码进行以下简单的分析:
我是用的是,第1行到第4行,都是为了将字符编码设置为。
第2行:引入包。
第4行:使用类库,以的方式请求网址,并将服务器返回的结果封装成一个对象,用变量来接收它。
第5行:一般可以根据状态码来判断是否请求成功,正常的状态码是200,异常状态码就很多了,比如404(找不到网页)、301(重定向)等。
第6行:打印网页的源码。注意,只是源码。不像是浏览器,在获取到源码之后,还会进一步地取请求源码中引用的图片等信息,如果有JS,浏览器还会执行JS,对页面显示的内容进行修改。使用requests进行请求,我们能够直接获取到的,只有最初始的网页源码。也正是因为这样,不加载图片、不执行JS等等,爬虫请求的速度会非常快。
代码很短吧?一行就完成了请求,可以,这很。
现在,运行一下代码看看吧。

箭头指向的是状态码,可以看到,200,请求正常。
被圈起来是网页的源码。
3.如何用python解析网页源码
网页源码我们拿到了,接下来就是要解析了。python解析网页源码有很多种方法,比如BeautifulSoup、正则、pyquery、xpath等。这里我简单介绍一下。
3.1 网页源码解析器
3.1.1 BeautifulSoup
这是我比较推荐的一款解析器,简单易用,容易理解。
但是使用bs4还需要安装另一个类库lxml,用来代替bs4默认的解析器。之所以这样做,是因为默认的那个实在太慢了,换用了lxml后,可以大幅度提升解析速度。
3.1.1.1 安装
命令行中输入以下指令并回车,安装bs4:
pip3 install beautifulsoup4

使用pip直接安装lxml会出错,所以要用些特别的方法。Windows用户的话,去百度搜一下lxml在Windows环境下的安装方法,网上有很多,我就不多说了(主要是嫌麻烦= =)。Ubuntu用户就很方便了,在终端里面输入以下指令并回车就行了:
apt-get install python-lxml

3.1.1.2 测试是否安装成功
进入python交互环境,引用bs4和lxml类库,不报错即安装成功。
import bs4import lxml

3.1.2 正则
这个不用安装,标准库里带的就有。
正则的优点:①速度快 ②能够提取有些解析器提取不到的数据
正则的缺点:①不够直观,很难从面向对象的角度来考虑数据的提取 ②你得会写正则表达式
教程就不放了,善用百度嘛。正则一般用来满足特殊需求、以及提取其他解析器提取不到的数据,正常情况下我会用bs4,bs4无法满足就用正则。
当然了,如果你喜欢,全部用正则解析也是没问题的,你喜欢就好= =。
3.1.3 pyquery
这个解析器的语法和jQuery很相似,所以写过jQuery的同学用起来可能比较容易上手。国内有个dalao写的爬虫框架pyspider用的就是这个解析器。
如果没用过jQuery,那就在bs4和pyquery两个里面选一个学吧,一般情况下会一个就够了。
3.1.3.1 安装
pip3 install pyquery
3.1.3.2 测试
import pyquery
3.2 使用BeautifulSoup+lxml解析网页源码
接着上面的代码来,我们使用BeautifulSoup+lxml解析请求到的网页源码。
从百度的首页,可以通过点击跳转到很多其他页面,比如说下面圈起来的,点击都会跳转到新的页面:

现在,我们想要用python获得从百度能够跳转到的页面的链接,该怎么做?
代码很简单,接着上面的写:
#coding=utf-8import requestsfrom 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属性,即这个对象指向的链接地址
首先,第3行,引入我们解析时要使用的类库,beautifulsoup4。
第9行,将网页的源码转化成了BeautifulSoup的对象,这样我们可以向操作DOM模型类似地去操作它。
第10行,从这个BeautifulSoup对象中,获取所有的a标签对象(大家应该知道a标签对象是什么吧,网页中的链接绝大多数都是a对象实现的),将他们组成一个列表,也就是a_list。
第11、12行,遍历这个列表,对于列表中的每一个a标签对象,获取它的属性href的值(href属性记录一个a标签指向的链接地址)。获取一个标签对象的属性,可以使用get('xx’)方法,比如a_tag是一个a标签对象,获取它的href的值,就是,获取它的class信息可以用,这将返回一个修饰该标签的class列表。
运行一下,可以看到,打印出了很多链接。

这是个简单的例子,介绍如何开始一个简单爬虫,不涉及复杂操作(复杂的后面会上小项目,会介绍)。关于beautifulsoup的详细用法,请自行百度。
3.3 简单的保存数据的方法
保存数据的方法大概可以分为几类:保存文本、保存二进制文件(包括图片)、保存到数据库。保存二进制文件和保存到数据库后面会具体说,这里简单讲一下怎么保存到文本。
python里面操作文本相当的简单。现在,我将刚才提取出来的链接保存到一个名称为的文本里面去,将上面的代码稍作修改。
#coding=utf-8import requestsfrom 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+' ' #加入到字符串中,并换行with open('url.txt','w') as f: #在当前路径下,以写的方式打开一个名为'url.txt',如果不存在则创建
f.write(text) #将text里的数据写入到文本中
代码中注释写得很清楚了,就不多做解释了。值得一提的是,使用with…as…来打开文件,在操作完成后,会自动关闭文件,不用担心忘记关闭文件了,超级好用啊!
运行一下代码,可以发现,当前路径下多了个名为的文件。

打开后,能够看到我们刚才提取出来的url。

4.更多
虽然东西不多,但是写了挺长时间的。因为平时在一边上课,一边实习,时间真的不多,抽着时间一点点写的。后面我尽量加快速度写吧,当然了,尽量嘛,写得慢了的话……你顺着网线过来打我呀~
我也只是个菜鸟,文中错误的地方,欢迎拍砖~
这篇教程主要讲如何将网络上的图片保存到本地来,以及如何利用接口解析完成动态页面的抓取。
本来是想依然用“妹子图”站点来演示的,结果点击去一看,没想到只过去一年,里面的图片却尺度越来越大了。。。还是算了。
看了一下,还是用“淘女郎”来演示吧,这也是一个用的比较多的例子。读完本文,你将掌握以下技能:
“淘女郎”爬虫的demo和逻辑
基于对接口的解析完成动态页面的抓取
使用python创建文件夹
将网络上的图片(或文件)保存到本地
基本的反ban措施
特别声明:本爬虫仅供学习使用,该博客撰写也仅出于学习目的,请勿用于商业活动,博主不承担相关法律责任。
1.创建项目
首先,在pycharm里面创建一个项目,就按站点域名来命名吧,。

创建完成之后,打开项目。然后创建新的py文件,命名为。相关代码将在里进行编写。

ok,接下来我们对要采集的站点进行研究。
2.研究目标站点
我们要采集的站点是“淘女郎”。百度搜索一下,就能够找到链接,比如当前的链接是https://mm.taobao.com/search_tstar_model.htm(如果淘宝的网站架构做了相关调整,这个链接可能会改变)。
页面大概是这个样子的:

我们想要获得的内容如下:淘女郎名字、所在地、以及该名模特发布的一系列图片。
其中,名字和所在地我们已经看到了,而点击该模特的肖像,就能进入该模特的主页,里面有她发布的一系列图片:

那么,如何来编写爬虫呢?
首先,我们回到主页上来。在主页上能够看到模特的列表,比如说第一个模特,名为“朱琳”。好的,让我们邮件点击页面,查看源码。然后在源码中搜索“朱琳”,你会看到:

什么鬼?为什么源码中找不到?

读过前面两篇博文,你应该知道,爬虫获取的内容是从源码中取得的。但是我在页面中明明看到了,在源码里面却没用,这怎么爬?
这就是动态页面抓取。当我们访问这个网址的时候,浏览器返回给我们的只有一部分信息。仔细观察源码就能发现,在本应该显示模特信息的地方,源码是这个样子的:

数据加载中……事实上,服务器是先给我们返回了这个页面,然后又通过ajax技术,请求了另外一个接口。接口返回了淘女郎们的信息,随后浏览器运行javascript代码,将这些信息填充到先前的页面中,因此,我们才在页面中看到了淘女郎们的信息。右键点击页面,选择“检查”,在“网络”一栏中能够看到真相(可能要刷新页面):

能够看到,共进行了两次请求,第一次是我们先前看到的源码,第二次就是淘女郎们相关信息。点开之后,右边有展示。我们需要的淘女郎姓名、所在地以及userId都能够从中获得(userId用于构建模特个人主页访问地址)。
嗯,大概是这个样子。开始写代码吧,其余的问题我们在编码的过程中进行探讨。
3.解析首页
在你对requests库和web请求过程有所了解后,你已经能够很轻松编写出请求首页接口的代码来:
# coding=utf-8import sysreload(sys)sys.setdefaultencoding('utf8')import requestsfrom bs4 import BeautifulSoupimport json
index_url = 'https://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8'def parse_index():"""
解析淘女郎首页接口
:return:
"""# 创建一个session
session = requests.Session()# 设置用于访问的请求头
headers = {'accept': 'application/json, text/javascript, */*; q=0.01','accept-encoding': 'gzip, deflate, br','accept-language': 'zh-CN,zh;q=0.9','content-type': 'application/x-www-form-urlencoded; charset=UTF-8','origin': 'https://mm.taobao.com','referer': 'https://mm.taobao.com/search_tstar_model.htm','user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36','x-requested-with': 'XMLHttpRequest',}# 设置post的数据
data = {'q': '','viewFlag': 'A','sortType': 'default','searchStyle': '','searchRegion': 'city:','searchFansNum': '','currentPage': '1','pageSize': '100',}# 进行请求,并用resp接收返回结果
resp = session.post(url=index_url, data=data,
headers=headers)# 打印返回结果print respprint resp.contentif __name__ == '__main__':
parse_index()
Session是requests库中的一个类,创建session对象进行访问的好处是,session对象能够自动维护访问的cookies信息。当然,它是不具备执行javascript代码的能力的,因此通过javascript修改的cookies信息它是记录不到的。
为什么要记录cookies?
因为有些站点,在你进行请求时,服务器可能会验证你的cookies信息。当cookies信息不正确时,服务器就会拒绝你的访问。
随后,我们设置了本次访问的headers和data,也就是请求消息头和post传递的数据。这些数据都可以从“检查”-“网络”-点击相关请求-"Headers"栏获得:

设置headers和data的目的是,为了将本次请求伪装成浏览器的请求,并通过传递的数据控制服务器返回我们需要的信息。更多的这里就不多说了,后面会在反ban的教程中做详细介绍。
接口的地址也是从这里获得的。
让我们运行一下代码,可以看到,访问成功了,返回的结果是一个json字符串。

接下来,我们进行解析吧。解析json字符串,我们需要用到Python的json库,它能够将一个json字符串转换为一个字典对象(或者反过来也可以)。我们将利用json库从返回的内容中提取需要的信息。
# 将json字符串转换成字典对象
data=json.loads(resp.content.decode('gb2312'))# 找到含有淘女郎信息的列表
data=data['data']['searchDOList']for mm in data:print mm['city']print mm['realName']print mm['userId']print '*'*80
需要注意的是,返回的消息的编码为,我们需要先进行decode,将其变为utf8,不然会出现乱码。现在运行一下代码看一下,我们已经成功提取出了信息:

测试没出现问题,那么,我们把代码稍微改一下,以适应接下来的步骤。修改后的代码稍后给出。
4.解析模特主页
请求模特主页的代码应该也不存在什么问题,跟上面那个是差不多的,区别只是一个用get一个用post而已。代码如下:
# coding=utf-8import sysreload(sys)sys.setdefaultencoding('utf8')import requestsfrom bs4 import BeautifulSoupimport jsonimport os
index_url = 'https://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8'base_path = 'images'def parse_index():"""
解析淘女郎首页接口
:return:
"""# 创建一个session
session = requests.Session()# 设置用于访问的请求头
headers = {'accept': 'application/json, text/javascript, */*; q=0.01','accept-encoding': 'gzip, deflate, br','accept-language': 'zh-CN,zh;q=0.9','content-type': 'application/x-www-form-urlencoded; charset=UTF-8','origin': 'https://mm.taobao.com','referer': 'https://mm.taobao.com/search_tstar_model.htm','user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36','x-requested-with': 'XMLHttpRequest',}# 设置post的数据
data = {'q': '','viewFlag': 'A','sortType': 'default','searchStyle': '','searchRegion': 'city:','searchFansNum': '','currentPage': '1','pageSize': '100',}# 进行请求,并用resp接收返回结果
resp = session.post(url=index_url, data=data,
headers=headers)# 打印返回结果print respprint resp.content# 将json字符串转换成字典对象
data = json.loads(resp.content.decode('gb2312'))# 找到含有淘女郎信息的列表
data = data['data']['searchDOList']for mm in data:
city = mm['city']
name = mm['realName']
userid = mm['userId']
next_url = 'https://mm.taobao.com/self/aiShow.htm?userId={}'.format(userid)
parse_mmpage(session, next_url, city, name)# 这里的break是用于测试的,只测试一个就跳出breakdef parse_mmpage(session, url, city, name):# 检查是否存在base_path目录,不存在则创建if not os.path.exists(base_path):
os.mkdir(base_path)# 检测是否存在当前 姓名-城市 的文件夹,不存在则创建
current_path = os.path.join(base_path, '{}-{}'.format(name, city).encode('gbk')) # windows下转成了gbk,linux下不需要if not os.path.exists(current_path):
os.mkdir(current_path)# 设置请求消息头
headers = {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','accept-encoding': 'gzip, deflate, br','accept-language': 'zh-CN,zh;q=0.9','referer': 'https://mm.taobao.com/search_tstar_model.htm','upgrade-insecure-requests': '1','user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',}# 请求页面
resp = session.get(url=url, headers=headers)# 打印结果print respprint resp.content.decode('gbk')if __name__ == '__main__':
parse_index()
解析淘女郎主页的for循环部分,为了方便测试,我只处理了一个就break了。
创建目录的时候,因为目前是在Windows上写的这篇博客,所以目录名字需要先转成gbk编码的,否则会出现乱码。如果在Linux上进行编写,则不用考虑这个问题。
请求模特主页源码时,因为该主页源码为gbk编码,所以需要先使用gbk对其解码。做爬虫的话,关于字符编解码的知识还是要知道的,不清楚的同学可以百度一下相关知识,这里就不多说了。
执行一下代码,可以看到,成功获得了网页源码:

而且也创建了相关目录(我使用的是相对路径,所以创建的文件夹位于当前项目根目录):

接下来,我们要从返回的网页源码中提取图片地址的列表。使用浏览器的“检查”功能对页面进行分析,发现主页上的照片均来自这个div。我们提取这个div中所有img的链接即可实现目标。
# 解码
content = resp.content.decode('gbk')# 构建beautifulsoup对象
bsobj = BeautifulSoup(content, 'lxml')# 获取div中所有的img对象
img_list = bsobj.find('div', {'class': 'mm-aixiu-content', 'id': 'J_ScaleImg'}).find_all('img')# 遍历img对象,获得其下载地址for img in img_list:
src = 'http:' + img.get('src')print src
运行一下,我们已经成功提取了图片链接:

5.下载图片
接下来就是下载图片了,可以这样写:
# coding=utf-8import sysreload(sys)sys.setdefaultencoding('utf8')import requestsfrom bs4 import BeautifulSoupimport jsonimport os
index_url = 'https://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8'base_path = 'images'def parse_index():"""
解析淘女郎首页接口
:return:
"""# 创建一个session
session = requests.Session()# 设置用于访问的请求头
headers = {'accept': 'application/json, text/javascript, */*; q=0.01','accept-encoding': 'gzip, deflate, br','accept-language': 'zh-CN,zh;q=0.9','content-type': 'application/x-www-form-urlencoded; charset=UTF-8','origin': 'https://mm.taobao.com','referer': 'https://mm.taobao.com/search_tstar_model.htm','user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36','x-requested-with': 'XMLHttpRequest',}# 设置post的数据
data = {'q': '','viewFlag': 'A','sortType': 'default','searchStyle': '','searchRegion': 'city:','searchFansNum': '','currentPage': '1','pageSize': '100',}# 进行请求,并用resp接收返回结果
resp = session.post(url=index_url, data=data,
headers=headers)# 打印返回结果print respprint resp.content# 将json字符串转换成字典对象
data = json.loads(resp.content.decode('gb2312'))# 找到含有淘女郎信息的列表
data = data['data']['searchDOList']for mm in data:
city = mm['city']
name = mm['realName']
userid = mm['userId']
next_url = 'https://mm.taobao.com/self/aiShow.htm?userId={}'.format(userid)
parse_mmpage(session, next_url, city, name)# 这里的break是用于测试的,只测试一个就跳出breakdef parse_mmpage(session, url, city, name):"""
解析模特主页
:param session:
:param url: 需要请求的地址
:param city: 模特所在的城市
:param name: 模特的姓名
:return:
"""# 检查是否存在base_path目录,不存在则创建if not os.path.exists(base_path):
os.mkdir(base_path)# 检测是否存在当前 姓名-城市 的文件夹,不存在则创建
current_path = os.path.join(base_path, '{}-{}'.format(name, city).encode('gbk')) # windows下转成了gbk,linux下不需要if not os.path.exists(current_path):
os.mkdir(current_path)# 设置请求消息头
headers = {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','accept-encoding': 'gzip, deflate, br','accept-language': 'zh-CN,zh;q=0.9','referer': 'https://mm.taobao.com/search_tstar_model.htm','upgrade-insecure-requests': '1','user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',}# 请求页面
resp = session.get(url=url, headers=headers)# 打印结果# 解码
content = resp.content.decode('gbk')# 构建beautifulsoup对象
bsobj = BeautifulSoup(content, 'lxml')# 获取div中所有的img对象
img_list = bsobj.find('div', {'class': 'mm-aixiu-content', 'id': 'J_ScaleImg'}).find_all('img')# 遍历img对象,获得其下载地址for img in img_list:
src = 'http:' + img.get('src')
download_img(src, current_path, '0.jpg')breakdef download_img(url, path, name):"""
下载一张图片
:param url: 图片地址
:param path: 图片保存路径
:param name: 图片保存名称
:return:
"""# 设置请求头
headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','Accept-Encoding': 'gzip, deflate','Accept-Language': 'zh-CN,zh;q=0.9','Host': 'img.alicdn.com','Upgrade-Insecure-Requests': '1','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',}# 下载图片
resp = requests.get(url, headers=headers)# 将图片以二进制的形式写入到文件中with open(os.path.join(path, name), 'wb') as f:
f.write(resp.content)if __name__ == '__main__':
parse_index()
为了方便测试,当前代码只下载第一页的第一位淘女郎的第一张图片。图片的请求头的获取与前面类似,把网址改成图片的地址就行了。运行查看结果:

6.完善代码
主要的编码已经完成了,并且在编码过程中验证了我们的代码时可行的。那么,现在我们来完善一下我们的代码,去除测试用代码,并完善其功能。
# coding=utf-8import sysreload(sys)sys.setdefaultencoding('utf8')import requestsfrom bs4 import BeautifulSoupimport jsonimport osimport logging# 首页接口地址index_url = 'https://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8'# 存放图片的主目录base_path = 'images'# 下载起始页start_page = 1# 下载终止页end_page = 2# 单个模特下载的最大图片数download_limit = 10# 创建一个loggerlogger = logging.getLogger()logger.setLevel(logging.INFO)formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')ch = logging.StreamHandler()ch.setLevel(logging.INFO)ch.setFormatter(formatter)logger.addHandler(ch)def parse_index(page_num, session):"""
解析淘女郎首页接口
:param page_num 要解析第几页
:return:
"""
logger.info(u"当前正在解析第{}页...".format(page_num))# 设置用于访问的请求头
headers = {'accept': 'application/json, text/javascript, */*; q=0.01','accept-encoding': 'gzip, deflate, br','accept-language': 'zh-CN,zh;q=0.9','content-type': 'application/x-www-form-urlencoded; charset=UTF-8','origin': 'https://mm.taobao.com','referer': 'https://mm.taobao.com/search_tstar_model.htm','user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36','x-requested-with': 'XMLHttpRequest',}# 设置post的数据
data = {'q': '','viewFlag': 'A','sortType': 'default','searchStyle': '','searchRegion': 'city:','searchFansNum': '','currentPage': '{}'.format(page_num),'pageSize': '100',}# 进行请求,并用resp接收返回结果
resp = session.post(url=index_url, data=data,
headers=headers)# 将json字符串转换成字典对象
data = json.loads(resp.content.decode('gbk'))# 找到含有淘女郎信息的列表
data = data['data']['searchDOList']for mm in data:
city = mm['city']
name = mm['realName']
userid = mm['userId']
next_url = 'https://mm.taobao.com/self/aiShow.htm?userId={}'.format(userid)
parse_mmpage(session, next_url, city, name)def parse_mmpage(session, url, city, name):"""
解析模特主页
:param session:
:param url: 需要请求的地址
:param city: 模特所在的城市
:param name: 模特的姓名
:return:
"""
logger.info(u'正在保存 {}-{} 的图片...'.format(city, name))# 检查是否存在base_path目录,不存在则创建if not os.path.exists(base_path):
os.mkdir(base_path)# 检测是否存在当前 姓名-城市 的文件夹,不存在则创建
current_path = os.path.join(base_path, '{}-{}'.format(name, city).encode('gbk')) # windows下转成了gbk,linux下不需要if not os.path.exists(current_path):
os.mkdir(current_path)# 设置请求消息头
headers = {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','accept-encoding': 'gzip, deflate, br','accept-language': 'zh-CN,zh;q=0.9','referer': 'https://mm.taobao.com/search_tstar_model.htm','upgrade-insecure-requests': '1','user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',}# 请求页面
resp = session.get(url=url, headers=headers)# 解码
content = resp.content.decode('gbk')# 构建beautifulsoup对象
bsobj = BeautifulSoup(content, 'lxml')# 获取div中所有的img对象
img_list = bsobj.find('div', {'class': 'mm-aixiu-content', 'id': 'J_ScaleImg'}).find_all('img')# 遍历img对象,获得其下载地址
cnt = 1for img in img_list:try:
src = 'http:' + img.get('src')except:continue
logger.info(u'正在保存第{}张图片...'.format(cnt))
cnt += download_img(src, current_path, '{}.jpg'.format(cnt))# 每个模特最多只下载download_limit张if cnt > download_limit:breakdef download_img(url, path, name):"""
下载一张图片
:param url: 图片地址
:param path: 图片保存路径
:param name: 图片保存名称
:return:
"""# 设置请求头
headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','Accept-Encoding': 'gzip, deflate','Accept-Language': 'zh-CN,zh;q=0.9','Host': 'img.alicdn.com','Upgrade-Insecure-Requests': '1','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',}# 下载图片
resp = requests.get(url, headers=headers)# 舍弃掉太小的图片,它可能是图标if len(resp.content) < 1000:return 0# 将图片以二进制的形式写入到文件中with open(os.path.join(path, name), 'wb') as f:
f.write(resp.content)return 1if __name__ == '__main__':# 创建一个session
session = requests.Session()# 从起始页到终止页,逐页抓取for x in range(start_page, end_page + 1):# 抓取一页
parse_index(x, session)
完善的部分:
可以设定抓取的起始页和终止页,程序会从起始页开始逐页抓取
可以设定每个模特抓取的最大图片数,抓取到指定数目后就会跳过
增加了logger部分,为程序添加必要的提示(当然,不添加程序执行会更快,但是不利于调试)
这个程序还有很多需要优化的地方,但作为一个简单的教程就只说这么多了,因为这篇博文的目的只是讲述和,此外还涉及到了一些反ban的东西,但是没有细讲,这部分内容后面会专门介绍。
更多
github地址请点这里。
emmm,终于写完了,博文写起来真心比写代码累多了= =
就这样吧,下一篇教程还不知道什么时候更,看情况吧= =(其实就是看心情嘛,23333)
Python3网络爬虫快速入门实战解析
一 前言
强烈建议:请在电脑的陪同下,阅读本文。本文以实战为主,阅读过程如稍有不适,还望多加练习。
本文的实战内容有:
网络小说下载(静态网站)
优美壁纸下载(动态网站)
视频下载
2020年,更多精彩内容,尽在微信公众号,欢迎您的关注:
二 网络爬虫简介
网络爬虫,也叫网络蜘蛛(Web Spider)。它根据网页地址(URL)爬取网页内容,而网页地址(URL)就是我们在浏览器中输入的网站链接。比如:https://www.baidu.com/,它就是一个URL。
在讲解爬虫内容之前,我们需要先学习一项写爬虫的必备技能:审查元素(如果已掌握,可跳过此部分内容)。
1 审查元素
在浏览器的地址栏输入URL地址,在网页处右键单击,找到检查。(不同浏览器的叫法不同,Chrome浏览器叫做检查,Firefox浏览器叫做查看元素,但是功能都是相同的)

我们可以看到,右侧出现了一大推代码,这些代码就叫做HTML。什么是HTML?举个容易理解的例子:我们的基因决定了我们的原始容貌,服务器返回的HTML决定了网站的原始容貌。

为啥说是原始容貌呢?因为人可以整容啊!扎心了,有木有?那网站也可以"整容"吗?可以!请看下图:

我能有这么多钱吗?显然不可能。我是怎么给网站"整容"的呢?就是通过修改服务器返回的HTML信息。我们每个人都是"整容大师",可以修改页面信息。我们在页面的哪个位置点击审查元素,浏览器就会为我们定位到相应的HTML位置,进而就可以在本地更改HTML信息。
再举个小例子:我们都知道,使用浏览器"记住密码"的功能,密码会变成一堆小黑点,是不可见的。可以让密码显示出来吗?可以,只需给页面"动个小手术"!以淘宝为例,在输入密码框处右键,点击检查。

可以看到,浏览器为我们自动定位到了相应的HTML位置。将下图中的password属性值改为text属性值(直接在右侧代码处修改):

我们让浏览器记住的密码就这样显现出来了:

说这么多,什么意思呢?浏览器就是作为客户端从服务器端获取信息,然后将信息解析,并展示给我们的。我们可以在本地修改HTML信息,为网页"整容",但是我们修改的信息不会回传到服务器,服务器存储的HTML信息不会改变。刷新一下界面,页面还会回到原本的样子。这就跟人整容一样,我们能改变一些表面的东西,但是不能改变我们的基因。
2 简单实例
网络爬虫的第一步就是根据URL,获取网页的HTML信息。在Python3中,可以使用urllib.request和requests进行网页爬取。
urllib库是python内置的,无需我们额外安装,只要安装了Python就可以使用这个库。
requests库是第三方库,需要我们自己安装。
requests库强大好用,所以本文使用requests库获取网页的HTML信息。requests库的github地址:https://github.com/requests/requests
(1) requests安装
在cmd中,使用如下指令安装requests:
pip install requests
或者:
easy_install requests
(2) 简单实例
requests库的基础方法如下:

官方中文教程地址:http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
requests库的开发者为我们提供了详细的中文教程,查询起来很方便。本文不会对其所有内容进行讲解,摘取其部分使用到的内容,进行实战说明。
首先,让我们看下requests.get()方法,它用于向服务器发起GET请求,不了解GET请求没有关系。我们可以这样理解:get的中文意思是得到、抓住,那这个requests.get()方法就是从服务器得到、抓住数据,也就是获取数据。让我们看一个例子(以 www.gitbook.cn为例)来加深理解:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://gitbook.cn/'
req = requests.get(url=target)
print(req.text)
requests.get()方法必须设置的一个参数就是url,因为我们得告诉GET请求,我们的目标是谁,我们要获取谁的信息。运行程序看下结果:

左侧是我们程序获得的结果,右侧是我们在www.gitbook.cn网站审查元素获得的信息。我们可以看到,我们已经顺利获得了该网页的HTML信息。这就是一个最简单的爬虫实例,可能你会问,我只是爬取了这个网页的HTML信息,有什么用呢?客官稍安勿躁,接下来进入我们的实战正文。
三 爬虫实战
1 小说下载
(1) 实战背景
小说网站-笔趣看:URL:http://www.biqukan.com/
本次实战就是从该网站爬取并保存一本名为《一念永恒》的小说。
(2) 小试牛刀
我们先看下《一念永恒》小说的第一章内容,URL:http://www.biqukan.com/1_1094/5403177.html

我们先用已经学到的知识获取HTML信息试一试,编写代码如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url=target)
print(req.text)
运行代码,可以看到如下结果:

可以看到,我们很轻松地获取了HTML信息。但是,很显然,很多信息是我们不想看到的,我们只想获得如右侧所示的正文内容,我们不关心div、br这些html标签。如何把正文内容从这些众多的html标签中提取出来呢?这就是本次实战的主要内容。
(3)Beautiful Soup
爬虫的第一步,获取整个网页的HTML信息,我们已经完成。接下来就是爬虫的第二步,解析HTML信息,提取我们感兴趣的内容。对于本小节的实战,我们感兴趣的内容就是文章的正文。提取的方法有很多,例如使用正则表达式、Xpath、Beautiful Soup等。对于初学者而言,最容易理解,并且使用简单的方法就是使用Beautiful Soup提取感兴趣内容。
Beautiful Soup的安装方法和requests一样,使用如下指令安装(也是二选一):
pip install beautifulsoup4
easy_install beautifulsoup4
一个强大的第三方库,都会有一个详细的官方文档。我们很幸运,Beautiful Soup也是有中文的官方文档。
URL:http://beautifulsoup.readthedocs.io/zh_CN/latest/
同理,我会根据实战需求,讲解Beautiful Soup库的部分使用方法,更详细的内容,请查看官方文档。
现在,我们使用已经掌握的审查元素方法,查看一下我们的目标页面,你会看到如下内容:

不难发现,文章的所有内容都放在了一个名为div的“东西下面”,这个"东西"就是html标签。HTML标签是HTML语言中最基本的单位,HTML标签是HTML最重要的组成部分。不理解,没关系,我们再举个简单的例子:
一个女人的包包里,会有很多东西,她们会根据自己的习惯将自己的东西进行分类放好。镜子和口红这些会经常用到的东西,会归放到容易拿到的外侧口袋里。那些不经常用到,需要注意安全存放的证件会放到不容易拿到的里侧口袋里。
html标签就像一个个“口袋”,每个“口袋”都有自己的特定功能,负责存放不同的内容。显然,上述例子中的div标签下存放了我们关心的正文内容。这个div标签是这样的:
<div id="content", class="showtxt">
细心的朋友可能已经发现,除了div字样外,还有id和class。id和class就是div标签的属性,content和showtxt是属性值,一个属性对应一个属性值。这东西有什么用?它是用来区分不同的div标签的,因为div标签可以有很多,我们怎么加以区分不同的div标签呢?就是通过不同的属性值。
仔细观察目标网站一番,我们会发现这样一个事实:class属性为showtxt的div标签,独一份!这个标签里面存放的内容,是我们关心的正文部分。
知道这个信息,我们就可以使用Beautiful Soup提取我们想要的内容了,编写代码如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url = target)
html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt') print(texts)
在解析html之前,我们需要创建一个Beautiful Soup对象。BeautifulSoup函数里的参数就是我们已经获得的html信息。然后我们使用find_all方法,获得html信息中所有class属性为showtxt的div标签。find_all方法的第一个参数是获取的标签名,第二个参数class_是标签的属性,为什么不是class,而带了一个下划线呢?因为python中class是关键字,为了防止冲突,这里使用class_表示标签的class属性,class_后面跟着的showtxt就是属性值了。看下我们要匹配的标签格式:
<div id="content", class="showtxt">
这样对应的看一下,是不是就懂了?可能有人会问了,为什么不是find_all('div’, id = 'content’, class_ = 'showtxt’)?这样其实也是可以的,属性是作为查询时候的约束条件,添加一个class_='showtxt’条件,我们就已经能够准确匹配到我们想要的标签了,所以我们就不必再添加id这个属性了。运行代码查看我们匹配的结果:

我们可以看到,我们已经顺利匹配到我们关心的正文内容,但是还有一些我们不想要的东西。比如div标签名,br标签,以及各种空格。怎么去除这些东西呢?我们继续编写代码:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url = target) html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt')
print(texts[0].text.replace('xa0'*8,' '))
find_all匹配的返回的结果是一个列表。提取匹配结果后,使用text属性,提取文本内容,滤除br标签。随后使用replace方法,剔除空格,替换为回车进行分段。 在html中是用来表示空格的。replace(’xa0’*8,’ ’)就是去掉下图的八个空格符号,并用回车代替:

程序运行结果如下:

可以看到,我们很自然的匹配到了所有正文内容,并进行了分段。我们已经顺利获得了一个章节的内容,要想下载正本小说,我们就要获取每个章节的链接。我们先分析下小说目录:
URL:http://www.biqukan.com/1_1094/

通过审查元素,我们发现可以发现,这些章节都存放在了class属性为listmain的div标签下,选取部分html代码如下:
<div class="listmain">
<dl>
<dt>《一念永恒》最新章节列表</dt>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15932394.html">第1027章 第十道门</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15923072.html">第1026章 绝伦道法!</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15921862.html">第1025章 长生灯!</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15918591.html">第1024章 一目晶渊</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15906236.html">第1023章 通天道门</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15903775.html">第1022章 四大凶兽!</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15890427.html">第1021章 鳄首!</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15886627.html">第1020章 一触即发!</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15875306.html">第1019章 魁祖的气息!</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15871572.html">第1018章 绝望的魁皇城</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15859514.html">第1017章 我还是恨你!</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/15856137.html">第1016章 从来没有世界之门!</a></dd>
<dt>《一念永恒》正文卷</dt> <dd><a href=https://www.360doc.cn/article/"/1_1094/5386269.html">外传1 柯父。</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/5386270.html">外传2 楚玉嫣。</a></dd> <dd><a href=https://www.360doc.cn/article/"/1_1094/5386271.html">外传3 鹦鹉与皮冻。</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/5403177.html">第一章 他叫白小纯</a></dd> <dd><a href=https://www.360doc.cn/article/"/1_1094/5428081.html">第二章 火灶房</a></dd>
<dd><a href=https://www.360doc.cn/article/"/1_1094/5433843.html">第三章 六句真言</a></dd> <dd><a href=https://www.360doc.cn/article/"/1_1094/5447905.html">第四章 炼灵</a></dd>
</dl>
</div>
在分析之前,让我们先介绍一个概念:父节点、子节点、孙节点。和限定了标签的开始和结束的位置,他们是成对出现的,有开始位置,就有结束位置。我们可以看到,在标签包含标签,那这个标签就是标签的子节点,标签又包含标签和标签,那么标签和标签就是标签的孙节点。有点绕?那你记住这句话:谁包含谁,谁就是谁儿子!
他们之间的关系都是相对的。比如对于标签,它的子节点是标签,它的父节点是标签。这跟我们人是一样的,上有老下有小。
看到这里可能有人会问,这有好多标签和标签啊!不同的标签,它们是什么关系啊?显然,兄弟姐妹喽!我们称它们为兄弟结点。
好了,概念明确清楚,接下来,让我们分析一下问题。我们看到每个章节的名字存放在了标签里面。标签还有一个href属性。这里就不得不提一下标签的定义了,标签定义了一个超链接,用于从一张页面链接到另一张页面。 标签最重要的属性是 href 属性,它指示链接的目标。
我们将之前获得的第一章节的URL和 标签对比看一下:
http://www.biqukan.com/1_1094/5403177.html
<a href=https://www.360doc.cn/article/"/1_1094/5403177.html">第一章 他叫白小纯</a>
不难发现, 标签中href属性存放的属性值/1_1094/5403177.html是章节URLhttp://www.biqukan.com/1_1094/5403177.html的后半部分。其他章节也是如此!那这样,我们就可以根据标签的href属性值获得每个章节的链接和名称了。
总结一下:小说每章的链接放在了class属性为listmain的标签下的标签中。链接具体位置放在html->body->div->dl->dd->a的href属性中。先匹配class属性为listmain的标签,再匹配标签。编写代码如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/'
req = requests.get(url = target)
html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
print(div[0])
还是使用find_all方法,运行结果如下:

很顺利,接下来再匹配每一个标签,并提取章节名和章节文章。如果我们使用Beautiful Soup匹配到了下面这个标签,如何提取它的href属性和标签里存放的章节名呢?
<a href=https://www.360doc.cn/article/"/1_1094/5403177.html">第一章 他叫白小纯</a>
方法很简单,对Beautiful Soup返回的匹配结果a,使用a.get('href’)方法就能获取href的属性值,使用a.string就能获取章节名,编写代码如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
server = 'http://www.biqukan.com/'
target = 'http://www.biqukan.com/1_1094/'
req = requests.get(url = target) html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
a_bf = BeautifulSoup(str(div[0]))
a = a_bf.find_all('a')
for each in a:
print(each.string, server + each.get('href'))
因为find_all返回的是一个列表,里边存放了很多的标签,所以使用for循环遍历每个标签并打印出来,运行结果如下。

最上面匹配的一千多章的内容是最新更新的12章节的链接。这12章内容会和下面的重复,所以我们要滤除,除此之外,还有那3个外传,我们也不想要。这些都简单地剔除就好。
(3)整合代码
每个章节的链接、章节名、章节内容都有了。接下来就是整合代码,将获得内容写入文本文件存储就好了。编写代码如下:
# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests, sys
"""
类说明:下载《笔趣看》网小说《一念永恒》
Parameters:
无
Returns:
无
Modify:
2017-09-13
"""
class downloader(object):
def __init__(self):
self.server = 'http://www.biqukan.com/'
self.target = 'http://www.biqukan.com/1_1094/'
self.names = []#存放章节名
self.urls = []#存放章节链接
self.nums = 0#章节数
"""
函数说明:获取下载链接
Parameters:
无
Returns:
无
Modify:
2017-09-13
"""
def get_download_url(self):
req = requests.get(url = self.target)
html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
a_bf = BeautifulSoup(str(div[0]))
a = a_bf.find_all('a')
self.nums = len(a[15:])#剔除不必要的章节,并统计章节数
for each in a[15:]:
self.names.append(each.string)
self.urls.append(self.server + each.get('href'))
"""
函数说明:获取章节内容
Parameters:
target - 下载连接(string)
Returns:
texts - 章节内容(string)
Modify:
2017-09-13
"""
def get_contents(self, target):
req = requests.get(url = target)
html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt')
texts = texts[0].text.replace('xa0'*8,' ')
return texts
"""
函数说明:将爬取的文章内容写入文件
Parameters:
name - 章节名称(string)
path - 当前路径下,小说保存名称(string)
text - 章节内容(string)
Returns:
无
Modify:
2017-09-13
"""
def writer(self, name, path, text):
write_flag = True
with open(path, 'a', encoding='utf-8') as f:
f.write(name + ' ')
f.writelines(text)
f.write(' ')
if __name__ == "__main__":
dl = downloader()
dl.get_download_url()
print('《一年永恒》开始下载:')
for i in range(dl.nums):
dl.writer(dl.names[i], '一念永恒.txt', dl.get_contents(dl.urls[i]))
sys.stdout.write(" 已下载:%.3f%%" % float(i/dl.nums) + ' ')
sys.stdout.flush()
print('《一年永恒》下载完成')
很简单的程序,单进程跑,没有开进程池。下载速度略慢,喝杯茶休息休息吧。代码运行效果如下图所示:

2 优美壁纸下载
(1)实战背景
已经会爬取文字了,是不是感觉爬虫还是蛮好玩的呢?接下来,让我们进行一个进阶实战,了解一下反爬虫。
URL:https://unsplash.com/

看一看这些优美的壁纸,这个网站的名字叫做Unsplash,免费高清壁纸分享网是一个坚持每天分享高清的摄影图片的站点,每天更新一张高质量的图片素材,全是生活中的景象作品,清新的生活气息图片可以作为桌面壁纸也可以应用于各种需要的环境。
看到这么优美的图片,我的第一反应就是想收藏一些,作为知乎文章的题图再好不过了。每张图片我都很喜欢,批量下载吧,不多爬,就下载50张好了。
(2)实战进阶
我们已经知道了每个html标签都有各自的功能。标签存放一下超链接,图片存放在哪个标签里呢?html规定,图片统统给我放到标签中!既然这样,我们截取就Unsplash网站中的一个标签,分析一下:
<img alt="Snow-capped mountain slopes under blue sky" src=https://www.360doc.cn/article/"https://images.unsplash.com/photo-91-cfac96e12253?dpr=1&auto=compress,format&fit=crop&w=360&h=240&q=80&cs=tinysrgb&crop=" class="cV68d" style="width: 220px; height: 147px;">
可以看到,标签有很多属性,有alt、src、class、style属性,其中src属性存放的就是我们需要的图片保存地址,我们根据这个地址就可以进行图片的下载。
那么,让我们先捋一捋这个过程:
使用requeusts获取整个网页的HTML信息;
使用Beautiful Soup解析HTML信息,找到所有标签,提取src属性,获取图片存放地址;
根据图片存放地址,下载图片。
我们信心满满地按照这个思路爬取Unsplash试一试,编写代码如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'https://unsplash.com/'
req = requests.get(url=target)
print(req.text)
按照我们的设想,我们应该能找到很多标签。但是我们发现,除了一些标签和一些看不懂的代码之外,我们一无所获,一个标签都没有!跟我们在网站审查元素的结果完全不一样,这是为什么?

答案就是,这个网站的所有图片都是动态加载的!网站有静态网站和动态网站之分,上一个实战爬取的网站是静态网站,而这个网站是动态网站,动态加载有一部分的目的就是为了反爬虫。
对于什么是动态加载,你可以这样理解:我们知道化妆术学的好,贼厉害,可以改变一个人的容貌。相应的,动态加载用的好,也贼厉害,可以改变一个网站的容貌。
动态网站使用动态加载常用的手段就是通过调用JavaScript来实现的。怎么实现JavaScript动态加载,我们不必深究,我们只要知道,动态加载的JavaScript脚本,就像化妆术需要用的化妆品,五花八门。有粉底、口红、睫毛膏等等,它们都有各自的用途。动态加载的JavaScript脚本也一样,一个动态加载的网站可能使用很多JavaScript脚本,我们只要找到负责动态加载图片的JavaScript脚本,不就找到我们需要的链接了吗?
对于初学者,我们不必看懂JavaScript执行的内容是什么,做了哪些事情,因为我们有强大的抓包工具,它自然会帮我们分析。这个强大的抓包工具就是Fiddler:
URL:http://www.telerik.com/fiddler
PS:也可以使用浏览器自带的Networks,但是我更推荐这个软件,因为它操作起来更高效。
安装方法很简单,傻瓜式安装,一直下一步即可,对于经常使用电脑的人来说,应该没有任何难度。
这个软件的使用方法也很简单,打开软件,然后用浏览器打开我们的目标网站,以Unsplash为例,抓包结果如下:

我们可以看到,上图左侧红框处是我们的GET请求的地址,就是网站的URL,右下角是服务器返回的信息,我们可以看到,这些信息也是我们上一个程序获得的信息。这个不是我们需要的链接,我们继续往下看。

我们发现上图所示的就是一个JavaScript请求,看右下侧服务器返回的信息是一个json格式的数据。这里面,就有我们需要的内容。我们局部放大看一下:

这是Fiddler右侧的信息,上面是请求的Headers信息,包括这个Javascript的请求地 址:http://unsplash.com/napi/feeds/home,其他信息我们先不管,我们看看下面的内容。里面有很多图片的信息,包括图片的id,图片的大小,图片的链接,还有下一页的地址。这个脚本以json格式存储传输的数据,json格式是一种轻量级的数据交换格式,起到封装数据的作用,易于人阅读和编写,同时也易于机器解析和生成。这么多链接,可以看到图片的链接有很多,根据哪个链接下载图片呢?先别急,让我们继续分析:

在这个网站,我们可以按这个按钮进行图片下载。我们抓包分下下这个动作,看看发送了哪些请求。
https://unsplash.com/photos/1PrQ2mHW-Fo/download?force=true
https://unsplash.com/photos/JX7nDtafBcU/download?force=true
https://unsplash.com/photos/HCVbP3zqX4k/download?force=true
通过Fiddler抓包,我们发现,点击不同图片的下载按钮,GET请求的地址都是不同的。但是它们很有规律,就是中间有一段代码是不一样的,其他地方都一样。中间那段代码是不是很熟悉?没错,它就是我们之前抓包分析得到json数据中的照片的id。我们只要解析出每个照片的id,就可以获得图片下载的请求地址,然后根据这个请求地址,我们就可以下载图片了。那么,现在的首要任务就是解析json数据了。
json格式的数据也是分层的。可以看到next_page里存放的是下一页的请求地址,很显然Unsplash下一页的内容,也是动态加载的。在photos下面的id里,存放着图片的id,这个就是我们需要获得的图片id号。
怎么编程提取这些json数据呢?我们也是分步完成:
获取整个json数据
解析json数据
编写代码,尝试获取json数据:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
req = requests.get(url=target) print(req.text)
很遗憾,程序报错了,问题出在哪里?通过错误信息,我们可以看到SSL认证错误,SSL认证是指客户端到服务器端的认证。一个非常简单的解决这个认证错误的方法就是设置requests.get()方法的verify参数。这个参数默认设置为True,也就是执行认证。我们将其设置为False,绕过认证不就可以了?

有想法就要尝试,编写代码如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
req = requests.get(url=target, verify=False)
print(req.text)
认证问题解决了,又有新问题了:

可以看到,我们GET请求又失败了,这是为什么?这个网站反爬虫的手段除了动态加载,还有一个反爬虫手段,那就是验证Request Headers。接下来,让我们分析下这个Requests Headers:

我截取了Fiddler的抓包信息,可以看到Requests Headers里又很多参数,有Accept、Accept-Encoding、Accept-Language、DPR、User-Agent、Viewport-Width、accept-version、Referer、x-unsplash-client、authorization、Connection、Host。它们都是什么意思呢?
专业的解释能说的太多,我挑重点:
User-Agent:这里面存放浏览器的信息。可以看到上图的参数值,它表示我是通过Windows的Chrome浏览器,访问的这个服务器。如果我们不设置这个参数,用Python程序直接发送GET请求,服务器接受到的User-Agent信息就会是一个包含python字样的User-Agent。如果后台设计者验证这个User-Agent参数是否合法,不让带Python字样的User-Agent访问,这样就起到了反爬虫的作用。这是一个最简单的,最常用的反爬虫手段。
Referer:这个参数也可以用于反爬虫,它表示这个请求是从哪发出的。可以看到我们通过浏览器访问网站,这个请求是从https://unsplash.com/,这个地址发出的。如果后台设计者,验证这个参数,对于不是从这个地址跳转过来的请求一律禁止访问,这样就也起到了反爬虫的作用。
authorization:这个参数是基于AAA模型中的身份验证信息允许访问一种资源的行为。在我们用浏览器访问的时候,服务器会为访问者分配这个用户ID。如果后台设计者,验证这个参数,对于没有用户ID的请求一律禁止访问,这样就又起到了反爬虫的作用。
Unsplash是根据哪个参数反爬虫的呢?根据我的测试,是authorization。我们只要通过程序手动添加这个参数,然后再发送GET请求,就可以顺利访问了。怎么什么设置呢?还是requests.get()方法,我们只需要添加headers参数即可。编写代码如下:
# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
headers = {'authorization':'your Client-ID'}
req = requests.get(url=target, headers=headers, verify=False)
print(req.text)
headers参数值是通过字典传入的。记得将上述代码中your Client-ID换成诸位自己抓包获得的信息。代码运行结果如下:

皇天不负有心人,可以看到我们已经顺利获得json数据了,里面有next_page和照片的id。接下来就是解析json数据。根据我们之前分析可知,next_page放在了json数据的最外侧,照片的id放在了photos->id里。我们使用json.load()方法解析数据,编写代码如下:
# -*- coding:UTF-8 -*-
import requests, json
if __name__ == '__main__':
target = 'http://unsplash.com/napi/feeds/home'
headers = {'authorization':'your Client-ID'}
req = requests.get(url=target, headers=headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
print('下一页地址:',next_page)
for each in html['photos']:
print('图片ID:',each['id'])
解析json数据很简单,跟字典操作一样,就是字典套字典。json.load()里面的参数是原始的json格式的数据。程序运行结果如下:

图片的ID已经获得了,再通过字符串处理一下,就生成了我们需要的图片下载请求地址。根据这个地址,我们就可以下载图片了。下载方式,使用直接写入文件的方法。
(3)整合代码
每次获取链接加一个1s延时,因为人在浏览页面的时候,翻页的动作不可能太快。我们要让我们的爬虫尽量友好一些。
# -*- coding:UTF-8 -*-
import requests, json, time, sys
from contextlib import closing
class get_photos(object):
def __init__(self):
self.photos_id = []
self.download_server = 'https://unsplash.com/photos/xxx/download?force=trues'
self.target = 'http://unsplash.com/napi/feeds/home'
self.headers = {'authorization':'Client-ID c94869b36aa272dd62dfaeefed769d4115fb3189a9d1ec88edbe626'}
"""
函数说明:获取图片ID
Parameters:
无
Returns:
无
Modify:
2017-09-13
"""
def get_ids(self):
req = requests.get(url=self.target, headers=self.headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
for each in html['photos']:
self.photos_id.append(each['id'])
time.sleep(1)
for i in range(5):
req = requests.get(url=next_page, headers=self.headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page']
for each in html['photos']:
self.photos_id.append(each['id'])
time.sleep(1)
"""
函数说明:图片下载
Parameters:
无
Returns:
无
Modify:
2017-09-13
"""
def download(self, photo_id, filename):
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}
target = self.download_server.replace('xxx', photo_id)
with closing(requests.get(url=target, stream=True, verify = False, headers = self.headers)) as r:
with open('%d.jpg' % filename, 'ab+') as f:
for chunk in r.iter_content(chunk_size = 1024):
if chunk:
f.write(chunk)
f.flush()
if __name__ == '__main__':
gp = get_photos()
print('获取图片连接中:')
gp.get_ids()
print('图片下载中:')
for i in range(len(gp.photos_id)):
print(' 正在下载第%d张图片' % (i+1))
gp.download(gp.photos_id[i], (i+1))
下载速度还行,有的图片下载慢是因为图片太大。可以看到右侧也打印了一些警报信息,这是因为我们没有进行SSL验证。

学会了爬取图片,简单的动态加载的网站也难不倒你了。赶快试试国内的一些图片网站吧!
3 视频下载
视频下载教程,请到这里查看:
https://cuijiahua.com/blog/2017/10/spider_tutorial_1.html
四 总结
本次Chat讲解的实战内容,均仅用于学习交流,请勿用于任何商业用途!
爬虫时效性低,同样的思路过了一个月,甚至一周可能无法使用,但是爬取思路都是如此,完全可以自行分析。
本次实战代码,均已上传我的Github,欢迎Follow、Star:https://github.com/Jack-Cherish/python-spider
如有问题,请留言。如有错误,还望指正,谢谢!
Python网络爬虫(一)爬虫基础
一、爬虫基础
1.HTTP基本原理
1.1URI和URL
URI,全称:Uniform Resource Identifier,即统一资源标志符;URL,全称:Universal Resource Locator,即统一资源定位符。
举例来说,https://github.com/favicon.ico是GitHub的网站图标链接,它是一个URL,也是一个URI。即有这样一个图标资源,我们用URL/URI来唯一指定它的访问方式,这其中包括了访问协议https、访问路径(/即根目录)和资源名称favicon.ico。通过这样一个链接,我们便可以从互联网上找到这个资源,这就是URL/URI。
URL是URI的子集,也就是说每个URL都是URI,但不是每个URI都是URL。URI包括一个子类叫做URN,它的全称为Universal Resource Name,即统一资源名称。URN只命名资源而不指定如何定位资源,比如urn:isbn:0指定了一本书的ISBN,可以唯一标识这本书,但是没有指定到哪里定位这本书,这就是URN。
但是在目前的互联网中,URN用得非常少,所以几乎所有的URI都是URL,一般的网页链接我们既可以称为URL,也可以称为URI。
1.2超文本
超文本,其英文名称叫作hypertext,我们在浏览器里看到的网页就是超文本解析而成的,其网页源代码是一系列HTML代码,里面包含了一系列标签,比如img显示图片,p指定显示段落等。浏览器解析这些标签后,便形成了我们平常看到的网页,而网页的源代码HTML就称作超文本。
打开浏览器按F12,会弹出浏览器的开发者模式,在Elements选项卡就可以看到网页的源代码,即超文本

1.3HTTP和HTTPS
在淘宝的首页,URL的开头会有http或https,这就是访问资源需要的协议类型。HTTP的全称是Hyper Text Transfer Protocol,中文名叫做超文本传输协议。HTTP协议是从网络传输超文本数据到本地浏览器的传送协议,它能保证高效而准确地传送超文本文档。HTTP由万维网协会(World Wide Web Consortium)和Internet工作小组IETF(Internet Engineering Task Force)共同合作制定的规范。
HTTPS的全称是Hyper Text Transfer Protocol over Secure Socket Layer,是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,简称为HTTPS。
HTTPS的安全基础是SSL,因此通过它传输的内容都是经过SSL加密的,它的主要作用是:
·建立一个信息安全通道来保证数据传输的安全
·确认网站的真实性,凡是使用了HTTPS的网站,都可以通过点击浏览器地址栏的锁头标志来查看网站认证之后的真实信息,也可以通过CA机构颁发的安全签章来查询
1.4HTTP请求过程
我们在浏览器中输入一个URL,回车之后便会在浏览器中观察到页面内容。实际上,这个过程是浏览器向网站所在的服务器发送了一个请求,网站服务器接收到这个请求后进行处理和解析,然后返回对应的响应,接着传回给浏览器。响应里包含了页面源代码等内容,浏览器再对其进行解析,便将网页呈现了出来。
打开Chrome浏览器,右击并选择“检查”项,即可打开浏览器的开发者工具。这里访问百度http://www.baidu.com/,输入该URL后回车,观察这个过程中发生了怎样的网络请求。可以看到,在Network页面下方出现了一个个条目,其中一个条目就代表一次发送请求和接收响应的过程,如下图所示

我们先观察第一个网络请求,即www.baidu.com
其中各列的含义如下。
–第一列Name:请求的名称,一般会将URL的最后一部分内容当作名称
–第二列Status:响应的状态码,这里显示为200,代表响应是正常的。通过状态码,我们可以判断发送了请求之后是否得到了正常的响应
–第三列Type:请求的文档类型。这里为document,代表我们这次请求的是一个HTML文档,内容就是一些HTML文档
–第四列Initiator:请求源:用来标记请求是由哪个对象或进程发起的
–第五列Size:从服务器下载的文件和请求的资源大小。如果是从缓存中取得的资源,则该列会显示from cache
–第六列Time:发起请求到获取响应所用的总时间
–第七列Waterfall:网络请求的可视化瀑布
点击这个条目,即可看到更详细的信息,如下图所示

首先是General部分,Request URL为请求的URL,Request Method为请求的方法,Status Code为状态响应码,Remote Address为远程服务器的地址和端口,Referrer Policy为Referrer判别策略
再继续往下,可以看到,有Response Headers和Request Headers,这分别代表响应和请求头。请求头里带有许多请求信息,例如浏览器标识、Cookies、Host等信息,这是请求的一部分,服务器会根据请求头内的信息判断请求是否合法,进而作出对应的响应。图中看到的Response Headers就是响应的一部分,例如其中包含了服务器的类型、文档类型、日期等信息,浏览器接受到响应后,会解析响应内容,进而呈现网络内容。
1.1.5 请求
请求,由客户端向服务端发出,可以分为4部分内容:请求方法(Request Method)、请求的网址(Request URL)、请求头(Request Headers)、请求体(Request Body)
(1)请求方法
常见的请求方法有两种:GET和POST
在浏览器中直接输入URL并回车,这便发起了一个GET请求,请求的参数会直接包含到URL里。例如,在百度中搜索Python,这就是一个GET请求,链接为https://www.baidu.com/s?wd=Python,其中URL中包含了请求的参数信息,这里参数wd表示要搜寻的关键字。POST请求大多在表单提交时发起。比如对于一个登录表单,输入用户名和密码之后,点击“登录”按钮,通常会发起一个POST请求,其数据通常以表单的形式传输,而不会体现在URL中
GET和POST请求方法有如下区别:
·GET请求中的参数包含在URL里,数据可以在URL中看到,而POST请求的URL不会包含这些数据,数据都是通过表单形式传输,会包含在请求体中
·GEt请求提交的数据最多只有1024字节,而POST方式没有限制
一般来说,登录时,需要提交用户名和密码,其中包含了敏感信息,使用GET方式请求的话,密码就会暴露在URL里,造成密码泄露,所以最好使用POST发送。上传文件时,由于文件内容比较大,也会选用POST方式
我们平常遇到的绝大部分请求都是GET或POST请求,另外还有一些请求方法,如HEAD、PUT、DELETE、OPTIONS、CONNECT、TRACE等
方法
描述
GET
请求页面,并返回内容
HEAD
类似于GET请求,只不过返回的响应中没有具体的内容,用于获取报头
POST
大多用于提交表单或上传文件,数据包含在请求中
PUT
从客户端向服务器传送的数据取代指定文档中的内容
DELETE
请求服务器删除指定的页面
CONNECT
把服务器当作跳板,让服务器代替客户端访问其他网页
OPTIONS
允许客户端查看服务器的性能
TRACE
回显服务器收到的请求,主要用于测试或诊断
(2)请求的网址
请求的网址,即统一资源定位符URL,它可以唯一确定我们想请求的资源
(3)请求头
请求头,用来说明服务器要使用的附加信息,比较重要的信息有Cookie、Referrer、User-Agent等
–Accept:请求报头域,用于指定客户端可接受哪些类型的信息
–Accept-Language:指定客户端可接受的语言类型
–Accept-Encoding:指定客户端可接受的内容编码
–Host:用于指定请求资源的主机IP和端口号,其内容为请求URL的原始服务器或网关的位置。从HTTP1.1版本开始,请求必须包含此内容
–Cookie:也常用复数形式Cookies,这是网站为了辨别用户进行会话跟踪而存储在用户本地的数据。它的主要功能是维持当前会话。例如,我们输入了用户名和密码成功登录了某个网站后,服务器会用会话保存登录状态信息,后面我们每次刷新或请求该站点的其他页面时,会发现都是登录状态,这就是Cookies的功劳。Cookies里有信息表示标识了我们所对应的服务器的会话,每次浏览器在请求该站点的页面时,都会在请求头中加上Cookies并将其发送给服务器,服务器通过Cookies识别出是我们自己,并且查出当前状态是登录状态,所以返回结果就是登录之后才能看到的网页内容
–Referer:此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计、防盗链处理等
–User-Agent:简称UA,它是一个特殊的字符串头,可以使服务器识别客户使用的操作系统及版本、浏览器及版本等信息。在做爬虫时加上此信息,可以伪装为浏览器;如果不加,很可能会被识别出为爬虫
–Content-Type:也叫互联网媒体类型(Internet Media Type)或者MIME类型,在HTTP协议消息头中,它用来表示具体请求的媒体类型信息。例如,text/html代表HTML格式,image/gif代表GIF图片,application/json代表JSON类型,更多对应关系可以查看此对照表:https://tool.oschina.net/commons
因此,请求头是请求的重要组成部分,在写爬虫时,大部分情况下都需要设定请求头
(4)请求体
请求体一般承载的内容是POST请求中的表单数据,而对于GET请求,请求体则为空
例如,下图是登录GitHub时捕获到的请求和相应

登录之前,我们填写了用户名和密码信息,提交时这些内容就会以表单数据的形式提交给服务器,此时需要注意Request Headers中指定Content-Type为application/x-www-form-urlencoded。只有设置Content-Type设置为application/json来提交JSON数据,或设置为multipart/form-data来上传文件。
下表为Content-Type和POST提交数据方式的关系
Content-Type
提交数据的方式
application/x-www-form-urlencoded
表单数据
multipart/form-data
表单文件上传
application/json
序列化JSON数据
text/xml
XML数据
在爬虫时,如果要构造POST请求,需要使用正确的Content-Type,并了解各种请求库的各个参数设置时使用的是哪种Content-Type,不然可能会导致POST提交后无法正常响应
1.1.6 响应
响应,由服务器返回给客户端,可以分为三部分:状态响应码(Response Status Code)、响应头(Response Headers)和响应体(Response Body)
(1)响应状态码
响应状态码表示服务器的响应状态,如200代表服务器正常响应,404代表页面未找到,500代表服务器内部发生错误。在爬虫中,我们可以根据状态码来判断服务器响应状态,如状态码200,则证明成功返回数据,再进行进一步的处理,否则直接忽略。
常见的错误代码及错误原因
状态码
说明
详情
100
继续
请求者应当继续提出请求。服务器已收到请求的一部分,正在等待其他部分
101
切换协议
请求者已要求服务器切换协议,服务器已确认并准备切换
200
成功
服务器已成功处理了请求
201
已创建
请求成功并且服务器创建了新的资源
202
已接受
服务器已接受请求,但尚未处理
203
非授权信息
服务器已成功处理了请求,但返回的信息可能来自另一个源
204
无内容
服务器成功处理了请求,但没有返回任何内容
205
重置内容
服务器成功处理了请求,内容被重置
206
部分内容
服务器成功处理了部分请求
300
多种选择
针对请求,服务器可执行多种操作
301
永久移动
请求的网页已永久移动到新位置,即永久重定向
302
临时移动
请求的网页暂时跳转到其他页面,即暂时重定向
303
查看其他位置
如果原来的请求是POST,重定向目标文档应该通过GET提取
304
未修改
此次请求返回的页面未修改,继续使用上次的资源
305
使用代理
请求者应该使用代理访问该页面
307
临时重定向
请求的资源临时从其他位置响应
400
错误请求
服务器无法解析该请求
401
未授权
请求没有进行身份验证或验证未通过
403
禁止访问
服务器拒绝此请求
404
未找到
服务器找不到请求的页面
405
方法禁用
服务器禁用了请求中指定的方法
406
不接受
无法使用请求的内容响应请求的页面
407
需要代理授权
请求者需要使用代理授权
408
请求超时
服务器请求超时
409
冲突
服务器在完成请求时发生冲突
410
已删除
请求的资源已永久删除
411
需要有效长度
服务器不接受不含有效内容长度标头字段的请求
412
未满足前提条件
服务器未满足请求者在请求中设置的其中一个前提条件
413
请求实体过大
请求实体过大,超出服务器的处理能力
414
请求URL过长
请求网址过长,服务器无法处理
415
不支持类型
请求格式不被请求页面支持
416
请求范围不符
页面无法提供请求的范围
417
未满足期望值
服务器未满足期望请求标头字段的要求
500
服务器内部错误
服务器遇到错误,无法完成请求
501
未实现
服务器不具备完成请求的功能
502
错误网关
服务器作为网关或代理,从上游服务器收到无效响应
503
服务不可用
服务器目前无法使用
504
网关超时
服务器作为网关或代理。但是没有及时从上游服务器收到请求
505
HTTP版本不支持
服务器不支持请求中所用的HTTP协议版本
(2)响应头
响应头包含了服务器对请求的应答信息,如Content-Type、Server、Set-Cookie等。下面简要说明常用的头信息
–Date:标识响应产生的时间
–Last-Modified:指定资源的最后修改时间
–Content-Encoding:文档类型,指定返回的数据类型是什么,如text/html代表返回HTML文档,application/x-javascript则代表返回JavaScript文件,image/jpeg则代表返回图片
–Set-Cookie:设置Cookies。响应头中的Set-Cookie告诉浏览器需要将此内容放在Cookies中,下次请求携带Cookies请求
–Expires:指定响应的过期时间,可以代理服务器或浏览器将加载的内容更新到缓存中。如果再次访问时,就可以直接从缓存中加载,降低服务器负载,缩短加载时间
(3)响应体
最重要的当属响应体的内容了。响应的正文数据都在响应体中,比如请求网页时,它的响应体就是网页的HTML代;请求一张图片时,它的响应体就是图片的二进制数据。我们做爬虫请求网页后,要解析的内容就是响应体

在浏览器开发者工具中点击Response,就可以看到网页的源代码,也就是响应体的内容,它是解析的目标。
在做爬虫时,我们主要通过响应体得到网页的源代码、JSON数据等,然后从中做出相应内容的提取
2.网页基础
2.1网页的组成
网页可以分为三大部分——HTML、CSS和JavaScript。如果把网页比作一个人的话,HTML相当于骨干,JavaScript相当于肌肉,CSS相当于皮肤,三者结合起来才能形成一个完善的网页。
(1)HTML
HTML是用来描述网页的一种语言,其全称叫作Hyper Text Markup Language,即超文本标记语言。网页包括文字、按钮、图片和视频等各种复杂的元素,其基础架构就是HTML。不同类型的文字通过不同类型的标签来表示,如图片用img标签来表示,视频用video标签来表示,段落用p标签来表示,它们之间的布局又常通过标签div嵌套组合而成,各种标签通过不同的排列和嵌套才形成了网页的框架
在Chrome浏览器中打开百度,右击并选择“检查”项(或按F12),打开开发者模式,这是在Element选项卡中即可看到网页的源代码

这就是HTML,整个网页就是由各种标签嵌套组合而成的。这些标签定义的节点元素相互嵌套和组合形成了复杂的层次关系,就形成了网页的架构
(2)CSS
HTML定义了网页的结构,但是只有HTML页面的布局观并不完美,可能还是简单的节点元素的排列,为了让网页更好看一些,这里借助了CSS
CSS,Cascading Style Sheets,即层叠样式表。“层叠”是指当在HTML中引用了数个样式文件,并且样式发生冲突时,浏览器能依据层叠顺序处理。“样式”指网页中文字大小、颜色、元素间距、排列等格式
CSS是目前唯一的网页页面排版样式标准,例如
#head_wrapper.s-ps-islite .s-p-top {position: absolute;bottom: 40px;width: 100%;height: 181px;}
大括号前面是一个CSS选择器。此选择器 的意思是首先选中id为head_wrapper且class为s-ps-islite的节点,然后再选中其内部的class为s-p-top的节点。大括号内部写的就是一条条样式规则,例如position指定了这个元素的布局方式为绝对布局,bottom指定元素的下边距为40像素,width指定了宽度为100%占满父元素,height则指定了元素的高度。
在网页中,一般会统一定义整个网页的样式规则,并写入CSS文件(.css)。在HTML中,只需要用link标签即可引入写好的CSS文件,这样整个页面就会变得美观、优雅。
(3)JavaScript
JavaScript,简称JS,是一种脚本语言。HTML和CSS配合使用,提供给用户的只是一种静态信息,缺乏交互性。我们在网页里可能会看到一些交互和动画效果,如下载进度条、提示框、轮播图扽话,这就是JavaScript的作用。实现了一种实时、动态、交互的页面功能
JavaScript通常也是以单独的文件形式加载的,后缀为js,在HTML中通过script标签即可引入,
如
<script src"jquery-2.1.0.js"></script>
总结一下,HTML定义了网页的内容和结构,CSS描述了网页的布局,JavaScript定义了网页的行为
2.2网页的结构
我们首先用例子来感受一下HTML的基本结构。新建一个文本文件。名称可以自取,后缀为html,内容如下:
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>This is a Demo</title></head><body><div id="container"><div class="wrapper"><h2 class="title">Hello World</h2><p class="text">Hello, this is a paragraph.</p></div></div></body></html>
这就是一个最简单的HTML实例。开头用DOCTYPE定义了文档类型,其次最外层是html标签,最后还有对应的结束标签来表示闭合,其内部是head标签和body标签,分别代表网页头和网页体,它们也需要结束标签。head标签内定义来一些页面的配置和引用,如 ,它指定了网页的编码为UTF-8.
title标签则定义来网页的标题,会显示在网页的选项卡中,不会显示在正文中。body标签则是在网页正文中显示的内容。div标签定义了网页区块,它的id是container,这是一个非常常用的属性,且id的内容在网页中是唯一的,我们可以通过它来获取这个区块。然后在此区块内又有一个div标签,它的class为wrapper,这也是一个非常常用的属性,经常与CSS配合使用来设定样式。然后此区块内部又有一个h2标签,这代表一个二级标题。另外,还有一个p标签,这代表一个段落。在这两者中直接写入相应的内容即可在网页中呈现出来,它们也有各自的class属性
我们将代码保存,在浏览器中打开该文件,就可以看到显示内容

可以看到,在选项卡上显示了This is a Demo字样,这是我们在head中title里定义的文字。而网页正文是body标签内部定义的各个元素生成的,可以看到这里显示了二级标题和段落
这个实例便是网页的一般结构。一个网页的标准形式是html标签内嵌套head和body标签,head内定义了网页的配置和引用,body内定义网页的正文。
2.3节点树及节点间的关系
在HTML中,所有标签定义的内容都是节点,它们构成了一个HTML DOM树。
我们先看下什么是DOM。DOM是W3C(万维网联盟)的标准,其英文全称Document Object Model,即文档对象模型。它定义了访问HTML和XML文档的标准:
W3C文档对象模型(DOM)是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。
W3C DOM标准被分为3个不同的部分
–核心DOM:针对任何结构文档的标准模型
–XML DOM:针对XML文档的标准模型
–HTML DOM:针对HTML文档的标准模型
根据W3C的HTML DOM标准,HTML文档中的所有内容都是节点。
–整个文档是一个文档节点
–每个HTML元素是元素节点
–HTML元素内的文本是文本节点
–每个HTML属性是属性节点
–注释是注释节点
通过HTML DOM,树的所有节点均可通过JavaScript访问,所有HTML节点元素均可被修改,也可以被创建或删除
节点树的节点彼此拥有层级关系。我们常用父(parent)、子(child)、和兄弟(sibling)等术语描述这些关系。父节点拥有子节点,同级的子节点被称为兄弟节点。在节点树中,顶端节点被称为根(root)。除了根节点之外,每个节点都有父节点,同时可拥有任意数量的子节点或兄弟节点。
2.4选择器
在CSS中,我们使用CSS选择器来定位节点。例如,上例中的div节点的id为container,那么就可以表示为#container,其中#开头代表选择id,其后紧跟id的名称。另外,如果我们想选择class为wrapper的节点,便可以使用.wrapper,这里以点(.)开头代表选择class,其后紧跟class的名称。另外还有一种选择方式,就是根据标签名筛选,例如想选择二级标题,直接用h2即可。这是最常用的3种表示,分别是id、class、标签名筛选,请牢记它们的写法
另外,CSS选择器还支持嵌套选择,各个选择器之间加上空格分隔开便可以代表嵌套关系,如#container .wrapper p则代表先选择id为container的节点,然后选中其内部的class为wrapper的节点,然后再进一步选中其内部的p节点。如果不另外加空格的话,则代表并列关系,如div#container .wrapper p.text代表先选择id为container的div节点,然后选中其内部的class为wrapper的节点,再进一步选中其内部的class为text的p节点。
下表为CSS选择器的其他语法规则
选择器
例子
例子描述
.class
.intro
选择class="intro"的所有节点
#id
#firstname
选择id="firstname"的所有节点
*
*
选择所有节点
element
p
选择所有p节点
element,element
div,p
选择所有div节点和所有p节点
element element
div p
选择div节点内部的所有p节点
element>element
div>p
选择父节点为div节点的所有p节点
element+element
div+p
选择紧接在div节点之后的所有p节点
[arttribute]
[target]
选择带有target属性的所有节点
[arttribute=value]
[target=blank]
选择target="blank"的所有节点
[arttribute~=value]
[title~=flower]
选择title属性包含单词flower的所有节点
:link
a:link
选择所有未被访问的链接
:visited
a:visited
选择所有已被访问的链接
:active
a:active
选择活动链接
:hover
a:hover
选择鼠标指针位于其上的链接
:focus
input:focus
选择获得焦点的input节点
:first-letter
p:first-letter
选择每个p节点的首字母
:first-line
p:first-line
选择每个p节点的首行
:first-child
p:first-child
选择属于父节点的第一个子节点的所有p节点
:before
p:before
在每个p节点的内容之前插入内容
:after
p:after
在每个p节点的内容之后插入内容
:lang(language)
p:lang
选择带有以it开头的lang属性值的所有p节点
element~element2
p~ul
选择前面有p节点的所有ul节点
[attribute^=value]
a[src^=“https”]
选择其src属性值以https开头的所有a节点
[attribute$=value]
a[src$=".pdf"]
选择其src属性以.pdf结尾的所有a节点
[attribute*=value]
a[src*=“abc”]
选择其src属性中包含abc子串的所有a节点
:first-of-type
p:first-of-type
选择属于其父节点的首个p节点的所有p节点
:last-of-type
p:last-of-type
选择属于其父节点的最后p节点的所有p节点
:only-of-type
p:only-of-type
选择属于其父节点的唯一的p节点的所有p节点
:only-child
p:only-child
选择属于其父节点的唯一子节点的所有p节点
:nth-child(n)
p:nth-child
选择属于其父节点的第二个子节点的所有p节点
:nth-last-child(n)
p:nth-last-child
同上,从最后一个子节点开始计数
:nth-of-type(n)
p:nth-of-type
选择属于其父节点第二个p节点的所有p节点
:nth-last-of-type(n)
p:nth-last-of-type
同上,但是从最后一个子节点开始计数
:last-child
p:last-child
选择属于其父节点最后一个子节点的所有p节点
:root
:root
选择文档的根节点
:empty
p:empty
选择没有子节点的所有p节点(包括文本节点)
:target
#news:target
选择当前活动的#news节点
:enabled
input:enables
选择每个启用的input节点
:disabled
input:disabled
选择每个禁用的input节点
:checked
input:checked
选择每个被选中的input节点
:not(selector)
:not
选择非p节点的所有节点
::selection
::seclection
选择被用户选取的节点部分
另外,还有一种比较常用的选择器是XPath,后续部分会提到
3.爬虫的基本原理
我们可以把互联网比作一张大网,而爬虫(即网络爬虫)便是在网上爬行的蜘蛛。把网的节点比作一个个网页,爬虫爬到这就相当于访问了该页面,获取了其信息。可以把节点间的连线比作网页与网页之间的链接关系,这样蜘蛛通过一个节点后,可以顺着节点继续爬行到达下一个节点,即通过一个网页继续获取后续的网页,这样整个网的节点便可以被蜘蛛全部爬取到,网站的数据就可以被抓取下来
3.1爬虫概述
简单来说,爬虫就是获取网页并提取和保存信息的自动化程序。
(1)获取网页
爬虫首先要做的工作就是获取网页,这里就是获取网页的源代码,源代码里包含了网页的部分有用信息,所以只要把源代码获取下来,就可以从中提取想要的信息了
前面讲了请求和相应的概念,向网站发送了一个请求,返回的响应体便是网页源代码。所以,关键在于构造一个请求并发送给服务器,然后接收到响应并将其解析出来。
在Python中,我们可以使用许多库来完成这个操作,如urllib、requests等。我们可以用这些库来帮助我们实现HTTP请求操作,请求和响应都可以用类库提供的数据结构来表示,得到响应之后只需解析数据结构中的Body部分即可,即得到网页的源代码。
(2)提取信息
获取网页源代码后,接下来就是分析网页源代码,从中提取我们想要的数据。首先,最通用的方法便是采用正则表达式,但是在构造正则表达式时比较复杂且容易出错
另外,由于网页的结构具有一定的规则,所以还有一些根据网页节点属性、CSS选择器或XPath来提取网页信息的库,如Beautiful Soup、pyquery、lxml等。使用这些库,我们可以高效快速地从中提取网页信息,如节点的属性、文本值等
提取信息是爬虫非常重要的部分,它可以使杂乱的数据变得条理清晰,以便我们后续处理和分析
(3)保存数据
提取信息后,我们一般会将提取到的数据保存到某处以便后续使用。这里保存形式有多种多样,如可以简单保存为TXT文本或JSON文本,也可以保存到数据库,如MySQL和MongoDB等,也可以保存至远程服务器,借助SFTP操作等
(4)自动化程序
说到自动化程序,意思是爬虫可以代替人来完成这些操作。首先,我们手工当然可以提取这些信息,但是当量特别大或者想快速获取大量数据的话,肯定还是要借助程序。爬虫就是代替我们完成这份爬取工作的自动化程序,它可以在抓取过程中进行各种异常处理、错误重试等操作,确保爬取持续高效地运行
3.2能抓怎样的数据
在网页中我们能看到各种各样的信息,最常见的便是常规网页,它们对应着HTML代码,而最常抓取的便是HTML源代码
另外,可能有些网页返回的不是HTML代码,而是一个JSON字符串(其中API接口大多采用这样的形式),这种格式的数据方便传输和解析,它们同样可以抓取,而且数据提取更加方便
此外,我们还可以看到各种二进制数据,如图片、视频和音频等。利用爬虫,我们可以将这些二进制数据抓取下来,然后保存成对应的文件名
另外,还可以看到各种二进制数据,如CSS、JavaScript和配置文件等,这些其实也是最普通的文件,只要在浏览器里面可以访问到,就可以将其抓取下来
3.3JavaScript渲染页面
有时候,我们在用urllib或requests抓取网页时,得到的源代码实际和浏览器中看到的不一样
这就是一个非常常见的问题。现在网页越来越多地采用Ajax、前端模块化工具来构建,整个网页可能都是由JavaScript渲染出来的,也就是说原始的HTML代码就是一个空壳,例如
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>This is a Demo</title></head><body><div id="container"></div></body><script src=https://www.360doc.cn/article/"app.js"></script></html>
body节点里面只有一个id为container的节点,但是需要注意在body节点后引入来app.js,它便负责整个网站的渲染
在浏览器中打开这个这个页面时,首先会加载这个HTML内容,接着浏览器会发现其中引入来一个app.js文件,然后便会接着去请求这个文件,获取到该文件后,便会执行其中的JavaScript代码,而JavaScript则会改变HTML中的节点,向其添加内容,最后得到完整的页面
但是在用urllib或requests等库请求当前页面时,我们得到的只是这个HTML代码,它不会帮助我们去加载这个JavaScript文件,这样也就看不到浏览器中的内容。
因此,使用基本HTTP请求库得到的源代码可能跟浏览器的页面中的页面源代码不太一样。对于这样的情况,我们可以分析其后台Ajax接口,也可以使用Selenium、Splash这样的库来实现模拟JavaScript渲染。
4.会话和Cookies
在浏览器网站的过程中,我们经常会遇到需要登录的情况,有些页面只有登录之后才可以访问,而且登录之后可以连续访问很多次网站,但是有时候过一段时间就需要重新登录。这就涉及到会话(Session)和Cookies的相关知识。
4.1静态网页和动态网页
开始之前,先了解一下静态网页和动态网页的概念。
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>This is a Demo</title></head><body><div id="container"><div class="wrapper"><h2 class="title">Hello World</h2><p class="text">Hello, this is a paragraph.</p></div></div></body></html>
这是最基本的HTML代码,我们将其保存为一个.html文件,然后把它放在某台具有固定公网IP的主机上,主机上装上Apache或Nginx等服务器,这样这台主机就可以作为服务器来,其他人便可以通过访问服务器看到这个页面,这就搭建了一个最简单的网站
这种网页的内容是HTML代码编写的,文字、图片等内容均通过写好的HTML代码来指定,这种页面叫做静态网页。它加载速度快,编写简单,但是存在很大的缺陷,如可维护性差,不能根据URL灵活多变地显示内容等。例如,我们想要给这个网页的URL传入一个name参数,让其在网页中显示出来,是无法做到的
因此,动态网页应运而生,他可以动态解析URL中参数的变化,关联数据库并动态呈现不同的页面内容,非常灵活。我们现在遇到的大多数网站都是动态网站,它们不再是一个简单的HTML,而是可能由JSP、PHP、Python等语言编写的,其功能比静态网页强大和丰富太多了
此外,动态网站还可以实现用户登录和注册的功能。再回到开头提到的问题,很多页面是需要登录之后才可以查看的。按照一般的逻辑来说,输入用户名和密码登录之后,肯定是拿到了一种凭证的东西,有了它,我们才能保持登录状态,才能访问登录之后才能看到的页面
4.2无状态HTTP
在了解会话和Cookies之前,我们还需要了解HTTP的一个特点,叫做无状态
HTTP的无状态是指HTTP协议对事务处理是没有记忆能力对,也就是说服务器不知道客户端是什么状态。当我们向服务器发送请求后,服务器解析此请求,然后返回对应的响应,服务器负责完成这个过程,而且这个过程是完全独立的,服务器不会记录前后状态的变化,也就是缺少状态记录。这意味这着如果后续需要处理前面的信息,则必须重传,这导致需要额外传递一些前面的重复请求,才能获取后续响应,然而这种效果显然不是我们想要的。为了保持前后状态,我们肯定不能将前面的请求全部重传一次,这太浪费资源了,对于这种需要登录的页面来说,更加棘手
这时两个用于保持HTTP连接状态的技术就出现了,它们分别是会话和Cookies。会话在服务端,也就是网站的服务器,用来保存用户的会话信息;Cookies在客户端,也可以理解为浏览器端,有了Cookies,浏览器在下次访问网页时会自动附带上它发送给服务器,服务器通过识别Cookies并鉴定出是哪个用户,然后再判断用户是否为是登录状态,然后返回对应的响应
我们可以理解为Cookies里面保存了登录的凭证,有了它,只需要在下次请求携带Cookies发送请求而不必重新输入用户名、密码等登录信息了
因此在爬虫中,有时候处理需要登录才能访问的页面时,我们一般会直接将登陆成功后获取的Cookies放在请求头里面直接请求,而不必重新模拟登录
(1)会话
会话,其本来的含义是指有始有终的一系列动作/消息。比如,打电话时,从拿起电话拨号到挂断电话这中间的一系列过程可以称为一个会话。
而在Web中,会话对象用来存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页面之间跳转时,存储在会话对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的Web页时,如果该用户还没有会话,则Web服务器将自动创建一个会话对象。当会话过期或被放弃后,服务器将终止该会话。
(2)Cookies
Cookies指某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据
·会话维持
当客户端第一次请求服务器时,服务器会返回一个请求头中带有Set-Cookie字段的响应给客户端,用来标记哪一个用户,客户端浏览器会把Cookies保存起来。当浏览器下一次再请求该网站时,浏览器会把此Cookies放到请求头一起提交给服务器,Cookies携带了会话ID信息,服务器检查该Cookies即可找到对应的会话是什么,然后再判断会话来以此辨别用户状态
在成功登录某个网站时,服务器会告诉客户端设置哪些Cookies信息,在后续访问页面时客户端会把Cookies发送给服务器,服务器再找到对应的会话加以判断。如果会话中的某些设置登录状态的变量是有效的,那就证明用户处于登录状态,此时返回登录之后才可以查看的网页内容,浏览器再进行解析便可以看到了
反之,如果传给服务器的Cookies是无效的,或者会话已经过期了,我们将不能继续访问页面,此时可能会收到错误的响应或者跳转到登录页面重新登录
所以,Cookies和会话需要配合,一个处于客户端,一个处于服务端,二者共同协作,就实现了登录会话控制
·属性控制
接下来,我们来看看Cookies都有哪些内容。这里以知乎为例,在浏览器开发者工具中打开Application选项卡,然后在左侧会有一个Storage部分,最后一项即为Cookies,将其点开,如下图所示,这些就是Cookies

可以看到,这里有很多条目,其中每个条目可以称为Cookie,它如下属性:
– Name:该Cookie的名称。一旦创建,该名称便不可更改。
– Value:该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码
–Domain:可以访问该Cookie的域名。例如,如果设置为.zhihu.com,则所有以zhihu.com结尾的域名都可以访问该Cookie
–Max Age:该Cookie失效的时间,单位为秒,也常和Expires一起使用,通过它可以计算出其有效时间。Max Age如果为正数,则该Cookie在Max Age秒之后失效。如果为负数,则关闭浏览器时Cookie即失效,浏览器也不会以任何形式保存该Cookie
– Path:该Cookie的使用路径。如果设置为/path/,则只有路径为/path/的页面可以访问该Cookie。如果设置为/,则本域名下的所有页面都可以访问该Cookie
– Size字段:此Cookie的大小
–HTTP字段:Cookie的httponly属性。若此属性为true,则只有在HTTP头中会带有此Cookie的信息,而不能通过document.cookie来访问此Cookie
–Secure:该Cookie是否仅被使用安全协议传输。安全协议有HTTPS和SSL等,在网络上传输数据之前先将数据加密。默认为false
·会话Cookie和持久Cookie
从表面意思来说,会话Cookie就是把Cookie放在浏览器内存里,浏览器在关闭之后该Cookie即失效;持久Cookie则会保存到客户端的硬盘,下次还可以继续使用,用于长久保持用户登录状态。
其实严格来说,没有会话Cookie和持久Cookie之分,只是由Cookie的Max Age或Expires字段决定了过期的时间
4.3常见误区
在谈论会话机制的时候,常常听到这样的误解——“只要关闭浏览器,会话就消失了”。可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客资料。对会话来说,也是一样,除非程序通知服务器删除一个会话,否则服务器会一直保留。比如,程序都是在我们做注销操作时才去删除会话
但是当我们关闭浏览器时,浏览器不会主动在关闭之前通知服务器它将要关闭,所以服务器根本不会有机会知道浏览器已经关闭。之所以会有这种错觉,是因为大部分会话机制都使用会话Cookie来保存会话ID信息,而关闭浏览器后Cookie就消失了,再次连接服务器时,也就无法找到原来的会话了,如果服务器设置的Cookies保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的Cookies发送给服务器,则再次打开浏览器,仍然能够找到原来的会话ID,,依旧还是可以保持登录状态
而且恰恰是由于关闭浏览器不会导致会话被删除,这就需要服务器为会话设置一个失效时间,当距离客户端上一次使用会话的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把会话删除以节省存储空间
5.代理的基本原理
我们在做爬虫的过程中经常会遇到这样的情况,最初爬虫正常运行,正常抓取数据,一切看起来都是那么美好,然而过一会就会出现错误,比如403Forbidden,这时候打开网页一看,可能会看到“您的IP访问频率太高”这样的提示。出现的原因是网站采取了一些反爬虫措施。比如,服务器会检测某个IP在单位时间内的请求次数,如果超过了这个阈值,就会直接拒绝服务,返回一些错误信息,这种情况可以称为封IP
既然服务器检测的是某个IP单位时间的请求次数,那么借助某种方式来伪装我们的IP,让服务器识别不出是由我们本机发起的请求,就可以成功防止封IP。一种有效的方式就是使用代理,后续会提到使用方式,先来了解代理的基本原理
5.1基本原理
代理实际上指的就是代理服务器,英文叫做proxy server,它的功能是代理网络用户去取得网络信息。形象地说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给Web服务器,Web服务器把相应信息传回给我们。如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥,此时本机不是直接向Web服务器发起请求,而是向代理服务器发起请求,请求会发送给代理服务器,然后由代理服务器再发送给Web服务器,接着由代理服务器再把Web服务器返回的响应转发给本机。这样我们同样可以正常访问网页,但这个过程中Web服务器识别出的真实IP就不再是我们本机的IP,就成功实现了IP伪装,这就是代理的基本原理
5.2代理的作用
那么,代理有什么作用呢?
1.突破自身IP访问限制,访问一些平时不能访问的站点
2.访问一些单位或团体内部资源;比如使用教育网内陆址段免费代理服务器,就可以用于对教育网开放的各类FTP下载上传,以及各类资料查询共享等服务
3.提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,当外界的信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同信息时,则直接由缓冲区中取出信息,传给用户,以提高访问速度
3.隐藏真实IP:上网者也可以通过这种方法隐藏自己的IP,免受攻击。对于爬虫来说,我们用代理就是为了隐藏自身IP,防止自身的IP被封锁
5.3爬虫代理
对于爬虫来说,由于爬虫爬取速度过快,在爬取过程中可能遇到同一个IP访问过于频繁的问题,此时网站就会让我们输入验证码登录或者直接封锁IP,这样会给爬取带来极大的不便
使用代理隐藏真实的IP,让服务器误以为是代理服务器在请求自己。这样在爬取过程中通过不断更换代理,就不会被封锁,可以达到很好的爬取效果
5.4代理分类
代理分类时,既可以根据协议区分,也可以根据其匿名程度区分
1.根据协议区分
根据代理的协议,代理可以分为如下类别
–FTP代理服务器:主要用于访问FTP服务器,一般有上传、下载以及缓存功能,端口一般为21、2121等
–HTTP代理服务器:主要用于访问网页,一般有内容过滤和缓存功能,端口一般为80、8080、3128等
–SSL/TLS代理:主要用于访问加密网站,一般有SSL或者TLS加密功能(最高支持128位加密强度),端口一般为443
–RTSP代理:主要用于访问Real流媒体服务器,一般有缓存功能,端口一般为554
– Telnet代理:主要用于telnet远程控制(黑客入侵计算机时常用于隐藏身份),端口一般为23
–POP3/SMTP代理:主要用于POP3/SMTP方式收发邮件,一般有缓存功能,端口一般为110/25
– SOCKS代理:只是单纯传递数据包,不关心具体协议和用法,所以速度快很多,一般有缓存功能,端口一般为8080.SOCKS代理协议又分为SOCKS4和SOCKS5,前者只支持TCP,而后者支持TCP和UDP,还支持各种身份验证机制、服务器端域名解析等。简单来说,SOCKS4能做到的SOCKS5都可以做到,但是SOCKS5能做到的SOCKS4不一定能做到
2.根据匿名程度区分
根据代理的匿名程度,代理可以分为如下类别
–高度匿名代理:会将数据包原封不动地转发,在服务端看来好像真的是一个普通客户端在访问,而记录的IP是代理服务器的IP
–普通匿名代理:会在数据包上做一些改动,服务端上有可能发现这是个代理服务器,也有一定几率追查到客户端的真实IP。代理服务器通常会加入的HTTP头有HTTP_VIA和HTTP_X_FORWARDED_FOR.
–透明代理:不但改动了数据包,还会告诉服务器客户端的真实IP。这种代理除了能缓存技术提高浏览速度,能用内容过滤提高安全性之外,并无其他显著作用,最常见的例子是内网中的硬件防火墙
–间谍代理:指组织或个人创建的用于记录用户传输的数据,然后进行研究、监控等目的的代理服务器
5.5常见代理设置
–使用网上的免费代理:最好使用高匿代理,另外可用的代理不多,需要在使用前筛选一下可用代理,也可以进一步维护一个代理池
– 使用付费代理服务:互联网上存在许多代理商,可以付费使用,质量比免费代理好很多
–ADSL拨号:拨一次号换一次IP,稳定性高,也是一种比较有效的解决方案
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/24976.html