爬虫是什么?
爬虫技术也称为网络爬虫、网络蜘蛛或网络机器人,是一种自动化获取互联网信息的程序或脚本。
爬虫技术通过程序自动访问网络资源,并将有用的数据抓取下来,存储到本地或远程服务器中。爬虫技术可以自动获取大量的数据,极大地提高了数据获取的效率和准确性,同时也为人们提供了更多的数据分析和挖掘的可能性。爬虫主要针对于网络网页,可以自动化浏览网络中的信息,或者说是一种网络机器人。它们被广泛用于互联网搜索引擎或其他类似网站,以获取或更新这些网站的内容和检索方式(概念来源于百度)
爬虫合法吗?
爬虫技术本身在法律上并不被禁止,但是利用爬虫技术获取数据的行为具有违法甚至犯罪的风险。
爬虫技术的合法性取决于其使用方式和应用范围。如果爬虫程序遵守网站设置的反爬虫措施,不侵犯他人知识产权、个人信息等,并且用于合法目的,如数据分析、市场研究等,通常不会构成违法行为。然而,如果爬虫技术用于非法获取或侵犯他人隐私、商业秘密等,或者干扰被访问网站的运营,甚至用于制作和传播计算机病毒等破坏性程序,这些行为就可能构成犯罪。
此外,爬虫技术在获取数据时,应遵守相关网站的robots协议,尊重网站的版权声明和隐私政策。总的来说,爬虫技术的使用必须在遵守法律规定和道德规范的前提下进行。
善意的爬虫:不破坏被爬取的网站的资源(正常访问,一般频率不高,不窃取用户隐私)
恶意的爬虫:影响网站的正常运营(抢票、秒杀、疯狂solo网站资源造成网站宕机)
爬虫的矛与盾
反爬机制:门户网站可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取。
反反爬策略:爬虫程序可以通过制定相关的策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的数据。
robots.txt协议:君子协议,规定了网站中哪些数据可以被爬虫爬取哪些数据不可以被爬取。
第一个爬虫小程序
需求:用程序模拟浏览器,输入一个网址,从该网址中获取到资源或者内容
python
from urllib.request import urlopen
url = "http://www.baidu.com"
resp = urlopen(url)
# 将读取到的内容保存到mybaidu.html文件中
with open("mybaidu.html", mode="w", encoding="utf-8")as f:
f.write(resp.read().decode("utf-8")) # 读取到网页的页面源代码
print("over!")
保存到文件的内容如下,读取到的是网页的源代码。
这样我们就成功的从百度上爬取到了一个网页的源代码,是不是超级简单呢?
使用requests进行爬虫
安装requests包:pip install requests
需求:在搜狗引擎中搜索周杰伦,拿到页面中的数据,页面搜索如下图:
按照如下代码运行:
这里只是非常简单的使用了一个get请求作为一个小演示
ini
import requests
url = "https://www.sogou.com/web?query=周杰伦"
resp = requests.get(url=url)
print(resp)
print(resp.text) # 拿到页面源代码
resp.close() #关闭请求
运行结果如下:
我们可以看到,运行结果出现了一点问题,并没有正确拿到页面中的源代码。这是什么原因呢?因为服务器检测到此次请求不是在正常浏览器中发送的,而是通过爬虫程序请求的。
要如何伪装的更像是浏览器正常发送的呢?
我们在浏览器中打开检查,找到第一个请求,在Requests Headers(请求头)中找到User-Agent,这个参数是描述当前的请求是通过哪个设备发送的,将这个参数添加到程序中。
改动之后的代码如下:
ini
import requests
url = "https://www.sogou.com/web?query=周杰伦"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
resp = requests.get(url=url, headers=headers)
print(resp)
print(resp.text) # 拿到页面源代码
resp.close() #关闭请求
运行之后发现结果正常,可以拿到页面上的源代码,这其实就是我们处理了一个小小的反爬
数据解析
上面我们基本掌握了抓取整个网页的基本技能,但是大多数情况下我们并不需要整个网页的全部内容,只是需要那么一小部分,这就涉及到了数据提取的问题。
1、正则表达式
Regular Expressiom,正则表达式,一种使用表达式的方式对字符串进行匹配的语法规则。
我们抓取到的网页源代码本质上就是一个超长的字符串,想从里面提取内容,用正则再合适不过了。
正则的优点:速度快,效率高,准确性高。
正则的缺点:新手上手难度有点儿高。
正则的语法: 使用元字符(具有固定含义的特殊符号)进行排列组合用来匹配字符串
常用元字符:
量词: 控制前面的元字符出现的次数
贪婪匹配和惰性匹配
这两个要着重的说一下,因为我们写爬虫用的最多的就是这个惰性匹配
案例:
str:玩儿吃鸡游戏,晚上一起上游戏,干什么呢?打游戏啊!
(1)reg:玩儿.*?游戏
此时的匹配结果:玩儿吃鸡游戏
(2)reg:玩儿.*游戏
此时的匹配结果:玩儿吃鸡游戏玩儿吃鸡游戏,晚上一起上游戏,干什么呢?打游戏啊!
2、re模块
(1)findall
: 匹配字符串中所有的符合正则的内容,返回的是一个列表
python
import re
# findall: 匹配字符串中所有的符合正则的内容,返回的是一个列表
lst = re.findall(r"\d+", "我的电话号码是:10086,我女朋友的电话号码是:10010")
print(lst)
运行结果:
(2)finditer
: 匹配字符串中所有的符合正则的内容,返回的是一个迭代器,从迭代器中拿到内容需要.group()
python
# finditer: 匹配字符串中所有的符合正则的内容,返回的是一个迭代器,从迭代器中拿到内容需要.group()
it = re.finditer(r"\d+", "我的电话号码是:10086,我女朋友的电话号码是:10010")
for i in it:
print(i.group())
运行结果:
注意:迭代器的效率要比列表高的多。
(3)search
:找到一个结果就返回,返回的结果是match对象,拿数据需要.group
python
# search,找到一个结果就返回,返回的结果是match对象,拿数据需要.group
s = re.search(r"\d+", "我的电话号码是:10086,我女朋友的电话号码是:10010")
print(s.group())
运行结果:
(4) match
:从头开始匹配
python
# match:从头开始匹配
m = re.match(r"\d+", "我的电话号码是:10086,我女朋友的电话号码是:10010")
print(m.group())
运行结果:匹配到的内容是空的
代码修改之后:
python
# match:从头开始匹配
m = re.match(r"\d+", "10086,我女朋友的电话号码是:10010")
print(m.group())
运行结果:
以上是爬虫过程中常用的方法,使用最多的是finditer
。
补充说明:
(1)预加载正则表达式,也就是提前写入一段正则表达式,在需要用到的时候直接拿来使用。
使用方法如下:
python
# 预加载正则表达式
obj = re.compile(r"\d+")
ret = obj.finditer("我的电话号码是:10086,我女朋友的电话号码是:10010")
for i in ret:
print(i.group())
(2)提取正则匹配结果中的一段固定的内容
例如:想匹配如下html内容中,全部的姓名
xml
s = '''
<div class='张三'><span id='1'>喜欢吃饭</span></div>
<div class='李四'><span id='2'>喜欢玩游戏</span></div>
<div class='王五'><span id='3'>喜欢踢足球</span></div>
<div class='赵四'><span id='4'>喜欢唱歌</span></div>
<div class='王麻子'><span id='5'>喜欢谈恋爱</span></div>
'''
代码如下:
xml
s = '''
<div class='张三'><span id='1'>喜欢吃饭</span></div>
<div class='李四'><span id='2'>喜欢玩游戏</span></div>
<div class='王五'><span id='3'>喜欢踢足球</span></div>
<div class='赵四'><span id='4'>喜欢唱歌</span></div>
<div class='王麻子'><span id='5'>喜欢谈恋爱</span></div>
'''
# 匹配以上字符串内容中,全部的姓名
# (?P<分组名字>正则表达式)可以单独从正则匹配的内容中进一步提取内容
obj = re.compile(r"<div class='(?P<name>.*?)'><span id='\d+'>.*?</span></div>", re.S) # re.S:让.能匹配换行符
result = obj.finditer(s)
for it in result:
print(it.group("name"))
运行结果:
小小实战一下
(1)案例1:爬虫豆瓣电影 Top 250,希望取到的内容为:电影名称、年份、评分、评价人数,并将数据保存成csv格式
页面源代码:
爬虫代码如下:
python
import requests
import re
import csv
url = "https://movie.douban.com/top250"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}
resp = requests.get(url=url, headers=headers)
page_content = resp.text
# 解析数据
obj = re.compile(r'<li>.*?<div class="item">.*?<span class="title">(?P<name>.*?)'
r'</span>.*? <p class="">.*?<br>(?P<year>.*?) '
r'.*?<span class="rating_num" property="v:average">(?P<score>.*?)</span>'
r'.*?<span>(?P<num>.*?)人评价', re.S)
result = obj.finditer(page_content)
f = open("data.csv", mode="w",encoding="utf-8")
csvwrite = csv.writer(f)
for it in result:
# # 将数据打印到控制台
# print(it.group("name"))
# # .strip()去掉年份前面的一大片空白
# print(it.group("year").strip())
# print(it.group("score"))
# print(it.group("num"))
# 将数据整理成字典的格式
dic = it.groupdict()
# 年份前面有空格,特殊处理
dic['year'] = dic['year'].strip()
csvwrite.writerow(dic.values())
f.close()
print("OVER!")
运行之后,查看csv文件中的内容:
(2)案例2:进入盗版电影天堂网站 www.dytt89.com/ 中,爬取2024必看热片中电影的名称和下载地址
首页的页面源代码:
子页面的页面源代码:
步骤:
(1)打开网站,定位到2024必看热片;
(2)从2024必看热片中提取到子页面的链接地址;
(3)请求子页面的链接地址,拿到我们想要的下载地址。
代码实现如下:
python
import requests
import re
domain = "https://www.dytt89.com/"
resp = requests.get(domain, verify=False) # verify=False 去掉安全验证
# 指定字符集,因为这个网站的编码格式为:charset=gb2312",如果不指定字符集,则拿到的网页源代码会乱码
resp.encoding = "gb2312"
# print(resp.text)
# 提取数据,拿到2024必看热片中ul里面的li
obj1 = re.compile(r"2024必看热片.*?<ul>(?P<li>.*?) </ul>", re.S)
# 提取每个li中的href
obj2 = re.compile(r"<a href='(?P<href>.*?)' title", re.S)
# 提取子页面电影的下载链接
obj3 = re.compile(
r'◎片 名(?P<movie>.*?)<br />.*?<td style="WORD-WRAP: break-word" bgcolor="#fdfddf"><a href="(?P<download>.*?)">',
re.S)
child_href_list = []
result1 = obj1.finditer(resp.text)
for it1 in result1:
li = it1.group("li")
# 提取子页面的链接
result2 = obj2.finditer(li)
for it2 in result2:
href = it2.group("href") # 这里取到的href是:/i/109813.html
# 拼接子页面的url地址:域名+子页面地址
child_href = domain + href.strip("/")
# 把子页面的链接保存到集合中
child_href_list.append(child_href)
# 提取子页面的内容,与上述大部分是重复的
for href in child_href_list:
child_resp = requests.get(url=href, verify=False)
child_resp.encoding = "gb2312"
# 这里用search是因为本来就只能匹配一个结果,如果用finditer还需要再写个循环,上面的finditer也可以改成search,匹配当前正则的都是只有一个结果
result3 = obj3.search(child_resp.text)
print(result3.group("movie"))
print(result3.group("download"))
# break # 加上break是为了循环只跑一次
print("OVER!")
运行结果:
我们可以看到电影名称和下载链接都取到了,自己也可以根据上面已讲述的方法,将数据写到文件中。到这里使用re模块的爬虫就结束了,是不是很有成就感呢?
看完了吧,请发表你的评论,我也不介意你点个赞,嘻嘻嘻~