爬虫是什么
爬虫介绍
爬虫的全称为网络爬虫,简称爬虫,别名有网络机器人,网络蜘蛛等等。
网络爬虫是一种自动获取网页内容的程序,为搜索引擎提供了重要的数据支撑。搜索引擎通过网络爬虫技术,将互联网中丰富的网页信息保存到本地,形成镜像备份。我们熟悉的谷歌、百度本质上也可理解为一种爬虫。
如果形象地理解,爬虫就如同一只机器蜘蛛,它的基本操作就是模拟人的行为去各个网站抓取数据或返回数据。
爬虫分类
网络爬虫一般分为传统爬虫和聚焦爬虫。
- 传统爬虫从一个或若干个初始网页的URL开始,抓取网页时不断从当前页面上抽取新的URL放入队列,直到满足系统的一定条件才停止,即通过源码解析来获得想要的内容。
- 聚焦爬虫需要根据一定的网页分析算法过滤与主题无关的链接,保留有用的链接并将其放入待抓取的URL队列,再根据一定的搜索策略从队列中选择下一步要抓取的网页URL,并重复上述过程,直到满足系统的一定条件时停止。另外,所有被爬虫抓取的网页都将会被系统存储、分析、过滤,并建立索引,以便之后的查询和检索;对于聚焦爬虫来说,这一过程所得到的分析结果还可能对以后的抓取过程给出反馈和指导。
防爬虫:KS-WAF(网站统一防护系统)将爬虫行为分为搜索引擎爬虫及扫描程序爬虫,可屏蔽特定的搜索引擎爬虫节省带宽和性能,也可屏蔽扫描程序爬虫,避免网站被恶意抓取页面。使用防爬虫机制的基本上是企业,我们平时也能见到一些对抗爬虫的经典方式,如:图片验证码、滑块验证、封禁 IP等等。
爬虫的基本流程
-
发起请求:
通过HTTP库向目标站点发起请求,即发送一个Request,请求可以包含额外的headers等信息,等待服务器响应。 -
获取响应内容:
如果服务器能正常响应,会得到一个Response,Response的内容便是所要获取的页面内容,类型可能有HTML,Json字符串,二进制数据(如图片视频)等类型。 -
解析内容:
得到的内容可能是HTML,可以用正则表达式、网页解析库进行解析。可能是Json,可以直接转为Json对象解析,可能是二进制数据,可以做保存或者进一步的处理。
Python语言中,我们经常使用Beautiful Soup、pyquery、正则表达式、lxml等,可以高效的从中获取网页信息,如节点的属性、文本值等。
Beautiful Soup库是解析、遍历、维护“标签树”的功能库,对应一个HTML/XML文档的全部内容。 -
保存数据:如果数据不多,可保存在txt 文本、csv文本或者json文本等。如果爬取的数据条数较多,可以考虑将其存储到数据库中。也可以保存为特定格式的文件。
保存后的数据可以直接分析,主要使用的库如下:NumPy、Pandas、 Matplotlib。
- NumPy:它是高性能科学计算和数据分析的基础包。
- Pandas : 基于 NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。它可以算得上作弊工具。
- Matplotlib:Python中最著名的绘图系统Python中最著名的绘图系统。它可以制作出散点图,折线图,条形图,直方图,饼状图,箱形图散点图,折线图,条形图,直方图,饼状图,箱形图等。
网页解析
三种方法
- 正则表达式
- lxml库
- BeautifulSoup
根据应用场景选择解析方法
针对文本的解析, 用正则表达式
针对html/xml的解析, 有Xpath、 BeautifulSoup、 正则表达式
针对JSON的解析, 有JSONPath
几种解析网页技术的区别
正则表达式基于文本的特征来匹配或查找指定的数据,可以处理任何格式的字符串文档,类似于模糊匹配的效果(re模块)
XPath和BeautifulSoup基于HTML/XML文档的层次结构来确定到达指定节点的路径,更适合出层级比较明显的数据
JSONPath专门用来JSON文档的数据解析
lxml库支持XPath语法的使用
json模块支持JSONPath语法的使用
BeautifulSoup本身就是一个Python库,是官方推荐的使用方法
几大解析工具的对比
解析工具 | 解析速度 | 使用难度 |
---|---|---|
BeautifulSoup | 最慢 | 最简单 |
lxml | 快 | 简单 |
正则表达式 | 最快 | 最难 |
XPath方法及使用
xpath(XML Path Language)是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历。
lxml方法及使用
lxml是一个HTML/XML的解析器,主要的功能是如何解析和提取HTML/XML数据
lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。
安装方式
pip install lxml
基本使用
form lxml import etree
from lxml import html # Python 3.5后使用方法
etree=html.etree
从字符串或文件中解析XML
etree模块提供了如下3个函数:
- fromstring()
- XML()
- HTML()
XML()函数的行为类似于fromstring()函数,通常用于将XML字面量直接写入到源码中,HTML()函数可以自动补全缺少的和标签
还可以调用parse()函数从XML文件中直接解析,在调用函数时,如果没有提供解析器,则使用默认的XML解析器,函数会返回一个ElementTree类的对象。
HTML文件不规范时,则需要自己指定解析器parser=etree.HTMLParser()
htmlEle=etree.parse(文件名,parser=parser)
对于Element对象
,使用tostring()
函数,将元素序列化
为XML树的编码字符串
表示形式
lxml库的相关类
- Element类: XML的结点
- ElementTree类: 一个完整的XML文档树
- ElementPath类: 可以理解为Xpath, 用于搜索和定位节点
Element类
- 节点操作
root=etree.Element(“book”) - 节点属性操作
a.root=etree.Element(“book”, interesting=‘totally’)
b. root.set(‘age’,‘30’) - 节点内文本的操作:通过text、tail属性或者xpath()方法来访问文本内容
ElementPath类
在ElementTree或者Element中,使用如下3个方法,可以满足搜索和查询需求,这3个方法的参数都是XPath语句
find()方法:返回匹配的第一个子元素
findall()方法:以列表形式返回所有匹配的子元素
iterfind()方法:返回一个所有匹配元素的迭代器html=etree.HTML(text)alist=html.findall(".//a[@href]")for a in alist: print(a.text)
lxml中使用XPath语法
使用lxml库中的路径表达式技巧,通过调用xpath()方法匹配选取的节点
- 获取所有li标签
html.xpath(“//li”)
- 获取所有li元素下的所有class属性的值
aattr=html.xpath("//li/@class")
- 获取li标签下href为www.baidu.com的a标签
alist=html.xpath("//li/a[@href=‘www.baidu.com’]")
- 获取最后一个li的a的href属性对应的值
aattr=html.xpath("//li[last()]/a/@href")
- 获取倒数第二个li元素的内容
t=html.xpath("//li[last()-1]/a") t[0].text
t=html.xpath("//li[last()-1]/a/text()")
代码案例
from lxml import etree
import time
import requests
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
}
Base_Domain = "https://www.dytt8.net"
def get_detail_urls(url):
"""获取该页的全部url"""
resp = requests.get(url, headers= HEADERS)
resp.encoding = resp.apparent_encoding
# text = resp.content.decode("gbk", "ignore")
text = resp.text
html = etree.HTML(text)
details_urls = html.xpath("//div[@class='co_content8']//table//a/@href")
#
details_urls= map(lambda urlID:Base_Domain+urlID, details_urls)
return details_urls
def paser_detail(detailurl):
movie = {}
resp = requests.get(detailurl, headers=HEADERS)
text = resp.content.decode("gbk", "ignore")
html = etree.HTML(text)
title=html.xpath("//div[@class='title_all']//font[@color='#07519a']/text()")[0]
# title = html.find("//div[@class='title_all']//font[@color='#07519a']/text()")
movie["title"] = title
zoomE = html.xpath("//div[@id='Zoom']")[0]
image= zoomE.xpath(".//img/@src")
movie["image"] = image
infos = zoomE.xpath(".//text()")
def parse_info(info, rule):
return info.replace(rule, "").strip()
for index, info in enumerate(infos):
if info.startswith("◎年 代"):
info = parse_info(info, "◎年 代")
movie["year"] = info
elif info.startswith("◎豆瓣评分"):
info = parse_info(info, "◎豆瓣评分")
movie["score"] = info
elif info.startswith("◎片 长"):
info = parse_info(info, "◎片 长")
movie["score"] = info
elif info.startswith("◎导 演"):
info = parse_info(info, "◎导 演")
movie["director"] = info
elif info.startswith("◎主 演"):
info = parse_info(info, "◎主 演")
actors = [info]
for x in range(index + 1, len(infos)):
actor = infos[x].strip()
actors.append(actor)
if actor.startswith("◎"):
break
movie["actor"] = actors
elif info.startswith("◎简 介"):
info = parse_info(info, "◎简 介")
for x in range(index + 1, len(infos)):
profile = infos[x].strip()
if profile.startswith("【下载地址】"):
break
movie["profile"] = profile
downurl = html.xpath("//td[@bgcolor='#fdfddf']/a/@href")[0]
movie["downurl"] = downurl
return movie
def spider(start, end):
base_url = "https://www.dytt8.net/html/gndy/dyzz/list_23_{}.html"
movies = []
#循环页数
for page in range(start, end):
print("---" * 15)
print("开始第{}页".format(page))
#构建新的带有页数的url
url = base_url.format(page)
#获取该页的详细url列表
detailurls = get_detail_urls(url)
#循环获取列表中的每一个电影
for x, detailurl in enumerate(detailurls):
print(" 第{0}页, 第{1}个url".format(page,x))
movie = paser_detail(detailurl)
print(" ",movie)
movies.append(movie)
time.sleep(1)
time.sleep(1)
if __name__ == "__main__":
spider(1,2)
BeautifulSoup4库
lxml只会局部遍历,而Beautiful Soup 是基于HTML DOM(Document Object Model)的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析HTML比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
BeauatifulSoup安装、使用
安装
pip install beautifulsoup4
使用
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello world</p>', 'lxml')
print(soup.p)
html.parser、lxml、xml、html5lib解析器区别
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, "html.parser") | Python的内置标准库执行速度适中文档容错能力强 | Python 2.7.3 or 3.2.2)前的版本中文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快文档容错能力强 | 需要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(markup, ["lxml-xml"]) BeautifulSoup(markup, "xml") | 速度快唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") | 最好的容错性以浏览器的方式解析文档生成HTML5格式的文档 | 速度慢不依赖外部扩展 |
bs4四个常用的对象
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
- Tag:
Tag 通俗点讲就是 HTML 中的一个个标签。利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。但是注意,它查找的是在所有内容中的第一个符合要求的标签。对于Tag,它有两个重要的属性,分别是name和attrs。 - NavigatableString:
NavigatableString,如果拿到标签后,还想获取标签中的内容。那么可以通过tag.string获取标签中的文字。 - BeautifulSoup:
BeautifulSoup对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag 对象,它支持遍历文档树和搜索文档树中描述的大部分的方法。 - Comment:
Comment对象是一种特殊类型的NavigableString对象
遍历文档树
contents和children(返回某个标签下的直接子元素,包括字符串。contents返回来的是列表,children返回的是迭代器)
示例
import requests
from bs4 import BeautifulSoup
res = requests.get(url="https://xn--9iq6jv3g.xofulitu9ok999.xyz/arttype/2000-2")
head_tag = BeautifulSoup(res.text,'lxml')
data = head_tag.select("div")[0]
#返回所有子节点的列表
print(data.contents)
#返回所有子节点的迭代器
for child in data.children:
print(child)
strings 和stripped_strings
如果tag中包含多个字符串,可以使用.string来循环获取
搜索文档树
find和find_all方法
搜索文档树,一般用得比较多的就是两个方法,一个是find,一个是find_all。
- find方法是找到第一个满足条件的标签后就立即返回,只返回一个元素。
- find_all方法是把所有满足条件的标签都选到,然后返回回去。使用这两个方法,最常用的用法是传入name以及attr参数找出符合要求的标签。
name参数
传入字符串:soup.find_all(“a”)
传入正则表达式:soup.find_all(re.compile("^b"))
传入列表:soup.find_all([“a”,“b”])
attrs参数
如果某个指定名字的参数不是搜索方法中内置的参数名,那么在进行搜索时,会把该参数当做指定名称的标签中的属性来搜索
bl=soup.find_all(id=“link1”)
bl=soup.find_all(href=re.compile(“elsie”),id=“link1”)
如果要搜索的标签名称为class,由于class是Python关键字,所以可以在class后面加一个下划线_
soup.find_all(“a”,class_=‘sister’)
有些标签的属性名称是不能使用的,比如html5中的“data-”属性,在程序中使用时,会出现SyntaxError异常信息。这时可以通过find_all()方法的attrs参数传入一个字典来搜索包含特殊属性的标签
soup.find_all(attrs={‘data-foo’:‘value’})
text参数
通过在find_all()方法中传入text参数,可以搜索文档中的字符串内容,与name参数一样,text参数可以接收字符串,正则表达式和列表等
limit参数
find_all方法接收limit参数,限制返回结果的数量,避免DOM树非常大时,搜素速度变慢
recursive参数
在调用find_all方法时,BeautifulSoup对象会检索当前节点的所有子节点。如果想检索当前节点的直接子节点,使用参数recursive=False即可
获取某个属性的值
通过下标方式获取 al=soup.find_all(“a”) for a in al: href=a[“href”]
通过attrs属性的方式al=soup.find_all(“a”) for a in al: href=a.attrs[“href”]
string、strings、stripped_strings、get_text()区别
- string:
获取某个标签下的非标签字符串,返回的是字符串,如果标签下有多行字符,则获取不到数据,可以使用contents获取 - strings:
获取某个标签下的子孙非标签字符串,并以生成器对象形式返回,可以直接把生成器转换为列表 - stripped_strings:
获取某个标签下的子孙非标签字符串,会去掉空白字符
select方法(使用css选择器的方式解析数据)
- 通过标签名查找:
soup.select(‘a’)
- 通过类名查找:应该在类的前面加一个.,
soup.select(’.sister’)
- 通过id查找:应该在id的名字前面加一个#号
soup.select(’#link1
- 组合查找:
组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开soup.select(“p #link1”)
直接子标签查找,则使用 > 分隔:soup.select(“head > title”)
- 通过属性查找:
查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到soup.select(‘a[href=“http://example.com/elsie”]’)
- 获取内容: select 方法返回的结果都是列表形式,可以遍历形式输出,然后用
get_text()
方法来获取它的内容