一、爬虫概述
什么是爬虫?
不知道各位是否遇到过这样的需求.就是我们总是希望能够保存互联网上的一些重要的数据信息为己所用。比如,
- 在浏览到一些优秀的让人血脉喷张的图片时.总想保存起来留为日后做桌面上的壁纸
- 在浏览到一些重要的数据时(各行各业),希望保留下来日后为自己进行各种销售行为增光添彩
- 在浏览到一些奇奇怪怪的劲爆视频时,希望保存在硬盘里供日后慢慢品鉴
- 在浏览到一些十分优秀的歌声曲目时,希望保存下来供我们在烦闷的生活中增添一份精彩
那么恭喜你.本课程将十分的适合于你.因为爬虫就是通过编写程序来爬取互联网上的优秀资源(图片,音频,视频,数据)
爬虫和Python
爬虫一定要用Python么?非也~用Java也行,C也可以.请各位记住,编程语言只是工具.抓到数据是你的目的.用什么工具去达到你的目的都是可以的。和吃饭一样,可以用叉子也可以用筷子,最终的结果都是你能吃到饭.那为什么大多数人喜欢用Python呢?答案:因为Python写爬虫简单.不理解?问:为什么吃米饭不用刀叉?用筷子?因为简单!好用!
而Python是众多编程语言中,小白上手最快,语法最简单.更重要的是,这货有非常多的关于爬虫能用到的第三方支持库.说直白点儿.就是你用筷子吃饭,我还附送你一个佣人.帮你吃!这样吃的是不是更爽了.更容易了~
爬虫合法么?
首先,爬虫在法律上是不被禁止的.也就是说法律是允许爬虫存在的.但是,爬虫也具有违法风险的.就像菜刀一样,法律是允许菜刀的存在的.但是你要是用来砍人,那对不起.没人惯着你.就像王欣说过的,技术是无罪的.主要看你用它来干嘛.比方说有些人就利用爬虫+一些黑客技术每秒钟对着bilibill撸上十万八千次.那这个肯定是不被允许的.
爬虫分为善意的爬虫和恶意的爬虫
- 善意的爬虫,不破坏被爬取的网站的资源(正常访问,一般频率不高,不窃取用户隐私)
- 恶意的爬虫,影响网站的正常运营(抢票,秒杀,疯狂solo网站资源造成网站宕机)
综上,为了避免进, 我们还是要安分守己.时常优化自己的爬虫程序避免干扰到网站的正常运行.并且在使用爬取到的数据时,发现涉及到用户隐私和商业机密等敏感内容时,一定要及时终止爬取和传播
爬虫的矛与盾
反爬机制 门户网站,可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取。
反反爬策略 爬虫程序可以通过制定相关的策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的数据。
robots.txt协议: 君子协议。规定了网站中哪些数据可以被爬虫爬取哪些数据不可以被爬取。
二、爬虫入门
2.1 第一个爬虫
from urllib.request import urlopen
url = 'http://www.baidu.com'
resp = urlopen(url)
print(resp.read().decode("utf-8"))
2.2 request模块入门
安装
pip3 install requests
pip3 install -i 第三方镜像地址 requests
import requests
url = "https://fanyi.baidu.com/sug"
hehe = {
"kw":input("请输入一个单词")
}
resp = requests.post(url, data=hehe)
print(resp. text)# 拿到的是文本字符串
print(resp.json())#此时拿到的直接是json数据
hehe = {'type'=13, 'start'='0'}
headers = {'User-Agent': "xxxx"}
resp = requests.get(url, param=hehe, headers=headers)
三、数据解析
数据解析概述
在上一章中,我们基本上掌握了抓取整个网页的基本技能.但是呢,大多数情况下,我们并不需要整个网页的内容,只是需要那么一小部分.怎么办呢?这就涉及到了数据提取的问题.
本课程中,提供四种解析方式:
- re解析
- bs4解析
- xpath解析
- pyquery解析
这四种方式可以混合进行使用,完全以结果做导向,只要能拿到你想要的数据.用什么方案并不重要.当你掌握了这些之后.再考虑性能的问题.
3.1 正则表达式
略
3.2 re模块
import re
r = re.findall("a", "我是abccddd")
print(r)
r = re.findall(r"\d+", "我是abccdd000123d")
print(r)
r = re.finditer(r"\d+", "我是ab18ccdd000123d") # 返回迭代器
for item in r: # 从迭代器中拿到内容
print(item.group()) # 从匹配结果中拿到数据
# search只会匹配到第一次匹配的内容
r = re.search(r"\d+", "我是ab18ccdd000123d")
# match, 在匹配的时候,是从字符串的开头进行匹配的,类似在正则前面加上了^
r = re.match(r"\d+", "我是ab18ccdd000123d")
# 预加载, 提取吧正则对象加载完毕
obj = re.compile(r"\d+")
r = obj.findall("我是ab18ccdd000123d")
## # 想要提取数据必须用小括号括起来,可以单独起名字
#(?P<名字>正则)
# 提取数据的时候,需要group("名字")
s = '''
<div class="西游记'><span id='10010">中国联通</span></div>
<div cLass='西游记"><span id='10086">中国移动</span></div>
'''
obj = re. compile(r"<span id="(?P<id>\d+)">(?P<name>.*?)</span>")
result = obj.finditer(s)
for item in result:
id = item.group("id")
print (id)
name = item.group ("name")
print (name)
实战: 获取豆瓣top250 电影
实战: 获取电影天堂信息
3.3 bs4使用
安装
pip3 install bs4
使用
from bs4 import BeautifulSoup
html = '''xx
'''
page = BeautifulSoup(html, "html.parser")
page.find() # 查找某个元素,只找第一个结果
page.find_all() # 找一堆结果
li = page.find('li', attrs={"id":"abc"})
a = li.find("a")
print(a.text)
print(a.get("href"))
bs实战
3.4 xpath实战
xpath解析
XPath是一门在XML文档中查找信息的语言.XPath可用来在 XML 文档中对元素和属性进行遍历.而我们熟知的HTML恰巧属于XML的一个子集.所以完全可以用xpath去查找html/中的内容.
首先,先了解几个概念.
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<author>
<nick>周大强</nick>
<nick>周芷若</nick>
</author>
</book>
在上述html中, html
- book, id, name, price…都被称为节点.
- ld, name. price, author被称为book的子节点
- book被称为id, name, price, author的父节点
- id, name, price,author被称为同胞节点
有了这些基础知识后,我们就可以开始了解xpath的基本语法了 在python中
安装:
pip3 install lxml
from lxml import etree
# from lxml import html
# etree.HTML()
et = etree.XML(xml)
et.xpath("/book") # / 表示根节点
et.xpath("/book/name/text()")[0] # text() : 拿文本
et.xpath("/book/*/text()")[0] # * 通配符,谁都行
et.xpath("/book/author/nick[@class='jay']/text()") # [] 表述属性筛选,@属性名=值
et.xpath("/book/partner/nick/@id") # 表示 nick里面id属性的内容
print(result)
result =
xpath实战案例: 猪八戒
3.5
安装
pip3 install pyquery
from pyquery import PyQuery
html = '''
<li><a href="http://www.baidu.com">百度</a></li>
'''
p = PyQuery(html)
# print(p)
# print(type(p))
# 链式操作
a = p("a")
a = p("li")("a")
text = p("#qq a").html() # 带html里面的内容
text = p("#qq a").text() # 拿文本
link = p("li")("a").attr("href") # 拿属性
print(a)
- .after(“txt”) : 在xx标签后面添加新标签
- .append(“txt”) :
- .remove_attr(“name”): 删除属性
- .remove(): 删除标签
pyquery 实战: 汽车之家
四、request进阶 初识反爬
requests进阶概述
我们在之前的爬虫中其实已经使用过headers了.header为HTTP协议中的请求头.一般存放一些和请求内容无关的数据.有时也会存放一些安全验证信息.比如常见的User-Agent, token, cookie等.
通过requests发送的请求,我们可以把请求头信息放在headers中.也可以单独进行存放,最终由requests自动帮我们拼接成完整的http请求头.
本章内容:
- 模拟浏览器登录->处理cookie
- 防盗链处理->抓取梨视频数据
- 代理->防止被封IP
- 接入第三方代理
4.1 处理cookie
# 必须得把上面的两个操作连起来
# 我们可以使用session进行请求 session你可以认为是一连串的请求,在这个过程中的cookie不会丢失
import requests
# 会话
session = requests.session()
data ={
"loginName": "18614075987",
"password":"q6035945"
# 1. 登录
url = "https://passport.17k.com/ck/user/login"
session.post(url, date=data)
# print (resp. text)
# print(resp. cookies) # 看cookie
# 2,拿书架上的数据
# 刚才的那个session中是有cookie的
resp = session.get("https://user.17k.com/ck/author/shelf?page=1&appKey=2222222")
print(resp.json())
r = request.get("https://user.17k.com/ck/author/shelf?page=1&appKey=2222222", headers={
'Cookie': "xxx",
"Referer": "link",
})
print(r.text)
实战2:抓取梨视频
代理
proxy = {
"http": "",
"https": "",
}
r = requests.get(url. proxies=proxy)
r.encoding = "utf-8"
print(r.text)
第三方代理接入
4.2
五、异步爬虫
5.1 多线程与多进程
多线程
多进程
何时用多进程和多线程
- 多线程: 任务相对统一,互相特别相似
- 多进程: 多个任务相互独立,很少有交集 比如 免费ip代理: 1
5.2 多线程
def func(name):
for i in range(10):
print(name, i)
if __name__ == '__main__':
func('aaa')
func('bbb')
func('ccc')
func('ddd')
上面代码多线程改写:
from threading import Thread
def func(name):
for i in range(10):
print(name, i)
if __name__ == '__main__':
# 创建线程
t1 = Tread(target = func, args=('aaa'))
t2 = Tread(target = func, args=('bbb'))
t3 = Tread(target = func, args=('ccc'))
t1.start()
t2.start()
t3.start()
print("我是主线程")
第二套多线程写法:面向对象
from threading import Thread
class MyThread(Thread):
def __init__(self, name):
super(MyThread, self).__init__()
self.name = name
def run(self):
for i in range(100):
print(self.name, i)
if __name__ == '__main__':
t1 = MyThread("aaa")
t2 = MyThread("bbb")
t3 = MyThread("ccc")
t1.start()
t2.start()
t3.start()
5.3 线程池
from concurrent.futures import ThreadPoolExecutor
def func(name):
for i in range(10):
print(name, i)
if __name__ == '__main__':
with ThreadPoolExecutor as t:
for i in range(100):
t.submit(func, f"周杰伦{i}")
)
from concurrent.futures import ThreadPoolExecutor
import time
def func(name,t):
time.sleep(t)
print("我是", name)
return name
if __name__ == '__main__':
with ThreadPoolExecutor(3) as t:
# t.submit().add_done_callback() 返回执行 callback函数
# 返回callback执行的顺序是不确定的
t.submit(func, "周杰伦", 2).add_done_callback(fn)
t.submit(func, "王力宏", 1).add_done_callback(fn)
t.submit(func, "ccc", 3).add_done_callback(fn)
# map返回值是生成器,返回的内容和任务分发的顺序是一致的
res = t.map(func, ["aaa",'bbb','ccc'], [2,1,3])
for r in res:
print(r)
线程池案例
5.4 多进程
from multiprocessing import Process
def func(name):
for i in range(100):
print(mame, i)
if __name__ == '__main__':
p1 = Process(target=func, args=("aaa",))
p2 = Process(target=func, args=("bbb",))
p1.start()
p2.start()
from multiprocessing import Process