参考B站UP主:路飞学城IT
实战一:网页采集器
UA:User-Agent(请求载体的身份标识)
UA检测:门户网站的服务器会检测对应请求的载体身份标识,如果检测到请求的载体身份标识为某一款浏览器
说明请求是一个正常的请求。但是,如果检测到请求的载体身份标识不是基于某一款浏览器的,则表示该请求
为不正常的请求(爬虫),则服务器端就很有可能拒绝该次请求
UA伪装:让爬虫对应的请求载体身份标识伪装成某一款浏览器
导包
import requests
if __name__ == "__main__":
UA伪装:将对应的User-Agent封装到一个字典中
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62'
}
网页地址
url = "https://www.sogou.com/web"
处理url携带的参数:封装到字典中
kw = input('enter a word:')
param = {
'query': kw
}
对指定的url发起的请求对应的是url携带参数的,并且请求过程中处理了参数
response = requests.get(url=url, params=param, headers=headers)
page_text = response.text
fileName = kw + '.html'
with open(fileName, 'w', encoding='utf-8') as fp:
fp.write(page_text)
print('爬取完毕')
实战二:破解百度翻译
导包
import requests
import json
if __name__ == "__main__":
指定url
post_url = "https://fanyi.baidu.com/sug"
进行UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62'
}
POST请求参数处理(同get请求一致)
word = input('enter a word:')
data = {
'kw': word
}
请求发送
response = requests.post(url=post_url, data=data, headers=headers)
获取响应数据:json()方法返回的是obj(如果确定响应数据是json类型的,才可以使用json())
dic_obj = response.json()
print(dic_obj)
进行持久化存储
fileName = word + '.json'
fp = open(fileName, 'w', encoding='utf-8')
json.dump(dic_obj, fp=fp, ensure_ascii=False)
print('success')
实战三:豆瓣电影的爬取
导包
import json
import requests
if __name__ == "__main__":
指定的url
url = "https://movie.douban.com/j/chart/top_list?"
处理携带url的参数,封装在字典里
param = {
'type': '24',
'interval_id': '100:90',
'action': '',
'start': '0', # 从库中的第几部电影
'limit': '20', # 一次取出的个数
}
UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62'
}
对指定的url发起请求对应的是url携带参数的,并且请求过程中处理了参数
response = requests.get(url=url, params=param, headers=headers)
list_data = response.json()
fp = open('./douban.json', 'w', encoding='utf-8')
json.dump(list_data, fp=fp, ensure_ascii=False)
print('success')
实战四:爬取KFC
导包
import json
import requests
if __name__ == "__main__":
指定的url
post_url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword"
进行UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62'
}
post请求处理
keyword = input('enter a word:')
data = {
'cname': '',
'pid': '',
'keyword': keyword,
'pageIndex': '1',
'pageSize': '10',
}
请求发送
response = requests.post(url=post_url, data=data, headers=headers)
获取响应数据:json()方法返回的是obj(如果确定响应数据是json类型的,才可以使用json)
post_text = response.json()
print(post_text)
进行持久化存储
fileName = keyword + '.json'
fp = open(fileName, 'w', encoding='utf-8')
json.dump(post_text, fp=fp, ensure_ascii=False)
print('success')
实战五:药监总局相关数据爬取
需求:爬取国家药品监督管理总局中基于中华人名共和国化妆品生产证相关数据
网址:http://scxk.nmpa.gov.cn:81/xk/
- 动态加载数据
- 首页中对应的企业信息数据通过ajax动态请求到的
http://scxk.nmpa.gov.cn:81/xk/itownet/portal/dzpz.jsp?id=b27810e3008a42abac35c345d88ceecc
http://scxk.nmpa.gov.cn:81/xk/itownet/portal/dzpz.jsp?id=c2cf1364781447e1a3b6dcd90bda6fff
通过对详情页url的观察发现:
- url的域名都是一样的,只有携带的参数(id)不一样
- id值可以从首页对应的ajax请求到的json串中获取
- 域名和id值拼接处一个完整的企业对应的详情页的url
详情页的企业详情数据也是动态加载出来的
- http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsById
- http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsById
- 观察后发现:所有的post请求的url都是一样的,只有参数id值是不同。如果我们可以批量获取多家企业的id后,就可以将id和url形成一个完整的详情页对应详情数据的ajax请求的url
导包
import requests
import json
if __name__ == "__main__":
指定url
url = "http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsList"
UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67'
}
存放id的列表
id_list = []
翻页部分,一页十五条数据
for page in range(1, 6):
data = {
# 'method': 'getXkzsList',
'on': 'true',
'page': 'page',
'pageSize': '15',
'productName': '',
'conditionType': '1',
'applyname': '',
'applysn': ''
}
dic_test = requests.post(url=url, data=data, headers=headers).json()
# print(dic_test)
for id in dic_test['list']:
id_list.append(id['ID'])
print(id['ID'])
# print(id_list)
获取企业详情数据
url = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsById'
for id in id_list:
data = {
'id': id
}
dic_test1 = requests.post(url=url, data=data, headers=headers).json()
print(dic_test1)
print('success')
数据解析
聚焦爬虫:爬取页面中指定的页面内容
编程流程:
- 指定url
- 发起请求
- 获取响应数据
- 数据解析
- 持久化存储
数据解析分类:
- 正则
- bs4
- xpath
数据解析原理概述:
- 解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
- 1、进行指定标签的定位
- 2、标签或者标签对应的属性中存储的数据值进行提取(解析)
常用正则表达式回顾
单字符:
. | 除换行以外所有字符 |
---|---|
[] | [aoe] [a-w] 匹配集合中任意一个字符 |
\d | 数字 [0-9] |
\D | 非数字 |
\w | 数字、字母、下划线、中文 |
\s | 所有的空白字符包、制表符、换页符等等。等价于 [ \f\n\r\t\v ] |
\S | 非空白 |
数量修饰:
* | 任意多次 >= 0 |
---|---|
+ | 至少1次 >= 1 |
? | 可有可无 0次或者1次 |
{m} | 固定m次 hello{3,} |
{m,} | 至少m次 |
{m,n} | m-n次 |
边界:
$ | 以某某结尾 |
---|---|
^ | 以某某开头 |
分组:
(ab)
贪婪模式:.*
非贪婪(惰性)模式:.*?
re.I:忽略大小写
re.M:多行匹配
re.S:单行匹配
re.sub(正则表达式,替换内容,字符串)
爬取网页图片
导包
import requests
if __name__ = "__main__":
图片的网址
url = "https://pic.qiushibaike.com/system/pictures/12460/124606217/medium/4B02IW7F6R4XTIZD.jpg"
content返回的是二进制形式的图片数据,text(字符串),content(二进制),json(对象)
img = requests.get(url=url).content
持久化存储
# 以wb的形式存储
with open('./qiutu.jpg', 'wb') as fp:
fp.write(img)
爬取一整张页面的图片
导包
import requests
import os
import re
if __name__ == "__main__":
创建一个文件夹,保存所有的图片
if not os.path.exists('./qiutuLibs'):
os.mkdir('./qiutuLibs')
页面网址
url = 'https://www.qiushibaike.com/imgrank/'
UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67'
}
使用通用爬虫对url对应的一整张页面进行爬取
page_text = requests.get(url=url, headers=headers).text
使用聚焦爬虫将页面中所有糗图进行解析/提取
ex = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>'
img_src_list = re.findall(ex, page_text, re.S)
# print(img_src_list)
for src in img_src_list:
# 拼接出一个完整的图片url
src = 'https:' + src
# 请求到了图片的二进制数据
img_data = requests.get(url=src, headers=headers).content
# 生成图片名称
img_name = src.split('/')[-1]
# 图片存储的路径
imgPath = './qiutuLibs/' + img_name
with open(imgPath, 'wb') as fp:
fp.write(img_data)
print(img_name, '下载成功!!!')
分页爬取
import requests
import re
import os
if __name__ == "__main__":
# 创建一个文件夹,保存所有的图片
if not os.path.exists('./qiutuLibs'):
os.mkdir('./qiutuLibs')
url = 'https://www.qiushibaike.com/imgrank/page/%d/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67'
}
for pageNum in range(1, 3):
new_url = format(url % pageNum)
# 使用通用爬虫对url对应的一整张页面进行爬取
page_text = requests.get(url=new_url, headers=headers).text
# 使用聚焦爬虫将页面中所有的糗图进行解析/提取
ex = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>'
img_src_list = re.findall(ex, page_text, re.S)
# print(img_src_list)
for src in img_src_list:
# 拼接出一个完整的图片url
src = 'https:' + src
# 请求到了图片的二进制数据
img_data = requests.get(url=src, headers=headers).content
# 生成图片名称
img_name = src.split('/')[-1]
# 图片存储的路径
imgPath = './qiutuLibs/' + img_name
with open(imgPath, 'wb') as fp:
fp.write(img_data)
print(img_name, '下载成功!!!')
bs4数据解析
数据解析的原理:
- 1、标签定位
- 2、提取标签,标签属性中存储的数据值
bs4数据解析的原理:
- 1、实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
- 2、通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
环境安装:
- pip install bs4
- pip install lxml(解析器)
实例化BeautifulSoup对象:
-
from bs4 import BeautifulSoup
-
对象的实例化:
- 1、将本地的html文档中的数据加载到该对象中。
from bs4 import BeautifulSoup if __name__ == "__main__": # 将本地的html文档中的数据加载到该对象中 fp = open('./sogou.html', 'r', encoding='utf-8') soup = BeautifulSoup(fp, 'lxml') print(soup)
- 2、将互联网上获取的页面源码加载到该对象中。
import requests from bs4 import BeautifulSoup if __name__ == "__main__": # 指定的url url = "https://www.sogou.com/" # 发起请求,get方法会返回一个响应对象 response = requests.get(url=url) # 获取响应对象,text返回的是字符串形式的响应数据 page_text = response.text soup = BeautifulSoup(page_text, 'lxml') print(soup)
-
提供的用于数据解析的方法和属性:
-
soup.tagName:返回的是文档第一次出现的tagName对应的标签
-
soup.find():
-
find(‘tagName’):等同于soup.div
-
属性定位:
- soup.find(‘div’, class_/id/attr=‘song’)
- soup.find_all(‘tagName’):返回符合要求的所有标签(列表)
-
-
-
select:
-
select(‘某种选择器(id,class,标签…选择器)’),返回的是一个列表
-
层级选择器:
- soup.select(’.tang > ul > li > a’):>表示的是一个层级
- soup.select(’.tang > ul a’):空格表示的是多个层级
-
-
获取标签之间的文本数据:
- soup.a.text:可以获取某一个标签中所有的文本内容
- soup.a.get_text():可以获取某一个标签中所有的文本内容
- soup.string:只可以获取该标签下面直系的文本内容
-
获取标签中的属性值:
- soup.a[‘href’]
爬取三国演义小说
导包
import requests
from bs4 import BeautifulSoup
if __name__ == "__main__":
网址url
url = 'https://www.shicimingju.com/book/sanguoyanyi.html'
UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.73'
}
get请求
page_text = requests.get(url=url, headers=headers)
page_text.encoding = 'utf-8'
page = page_text.text
在首页中解析出章节的标题和详情页的url
1、实例化BeautifulSoup对象,需要将页面源码数据加载到该对象中
soup = BeautifulSoup(page, 'lxml')
解析章节标题和详情页的url
li_list = soup.select('.book-mulu > ul > li')
持久化存储在本地
fp = open('./sanguo.txt', 'w', encoding='utf-8')
对每个标题的内容进行爬取
for li in li_list:
title = li.a.string
detail_url = 'https://www.shicimingju.com/' + li.a['href']
# 对详情页发起请求,解析出章节内容
detail_page_text = requests.get(url=detail_url, headers=headers)
detail_page_text.encoding = 'utf-8'
detail_page = detail_page_text.text
# 解析出详情页中相关的章节内容
detail_soup = BeautifulSoup(detail_page, 'lxml')
div_tag = detail_soup.find('div', class_ = 'chapter_content')
# 解析到了章节内容
content = div_tag.text
fp.write(title + '+' + content + '\n')
print(title + ' 爬取成功')
爬取霸道总裁爱上替嫁娇妻
需求:爬取霸道总裁爱上替嫁娇妻的小说标题和内容
和爬取三国演义基本相似,但需要注意class的名称,和一些细节
import requests
from bs4 import BeautifulSoup
if __name__ == "__main__":
# 需求网址
url = 'https://www.kefulai.com/badaozongcaiaishangtijiajiaoqi/'
# UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.73'
}
# get请求
page = requests.get(url=url, headers=headers)
page.encoding = 'utf-8'
page_text = page.text
# print(page_text)
# 1、实例化BeautifulSoup对象,需要将页面源码数据加载到该对象中
soup = BeautifulSoup(page_text, 'lxml')
# 解析章节标题和详情页的url,简介
introduction = soup.find('p', attrs={'content-introduce'}).get_text()
# print(introduction)
li_list = soup.select('.content-list > ul > li')
# print(li_list)
# 持久化存储在本地
fp = open('./bdzc.txt', 'w', encoding='utf-8')
for li in li_list:
# 标题内容
title = li.a.string
# 小说内容页面的网址
detail_url = li.a['href']
detail_page_text = requests.get(url=detail_url, headers=headers)
detail_page_text.encoding = 'utf-8'
detail_page = detail_page_text.text
# 解析出详情页中相关的章节内容
detail_soup = BeautifulSoup(detail_page, 'lxml')
div_tag = detail_soup.find('div', class_='article')
# 解析到了章节内容
content = div_tag.text
fp.write(title + ' ' + content + '\n')
print(title + ' 爬取成功')
xpath解析
xpath解析:最常用且最便捷高效的一种解析方式。通用性。
-
xpath解析原理:
- 1、实例化一个etree的对象,且需要将解析的页面源码数据加载到该对象中。
- 2、调用etree对象中xpath方法结合着xpath表达式实现标签的定位和内容的捕获。
-
环境的安装:pip install lxml
-
如何实例化一个etree对象:from lxml import etree
-
1、将本地的html文档中的源码数据加载到etree对象中:
- etree.parse(filepath)
-
2、可以将从互联网上获取的源码数据加载到该对象中
- etree.html(‘page_text’)
-
xpath(‘xpath表达式’)
-
-
xpath表达式:
-
/ : 表示的是从根节点开始定位。表示的是一个层级。
-
// :表示的是多个层级。可以表示从任意位置开始定位。
-
属性定位://div[@class=“song”] tag[@attrName=“attrValue”]
-
索引定位://div[@class=“song”]/p[3] 索引是从1开始的。
-
取文本:
- /text():获取的是标签中直系的文本内容
- //text():标签中非直系的文本内容(所有的文本内容)
-
取属性:
/@attrName ----> img/src
-
from lxml import etree
if __name__ == "__main__":
# 实例化好了一个etree对象,且将被解析的源码加载到了该对象中
tree = etree.parse('test.html')
r = tree.xpath('/html/body/div') # 精准查找
r = tree.xpath('/html//div') # 找到html下的所有div
r = tree.xpath('//div') # 找到代码中所有的div
r = tree.xpath('//div[@class="song"]') # 返回 class="song"的div标签的内容
r = tree.xpath("//div[@class='tang']//li[5]/a/test()")[0]
r = tree.xpath('//div[@class="tang"]//text()')
r = tree.xpath('//li//text()')
r = tree.xpath('//div[@class="tang"]//text()')
r = tree.xpath('//div[@class="song"]/img/@src')
xpath爬取58二手房标题信息
导包:
import requests
from lxml import etree
if __name__ == "__main__":
需要爬取的url:
url = "https://bj.58.com/ershoufang/"
UA伪装:
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84"
}
通过Get请求获取响应数据:
page_text = requests.get(url=url, headers=headers).text
数据解析(本地用parse,互联网用HTML)
etree.HTML():构造了一个XPath解析对象并对HTML文本进行自动修正。
tree = etree.HTML(page_text)
存储的就是div标签对象
div_list = tree.xpath('//section[@class="list"]/div')
# print(div_list)
fp = open('58.txt', 'w', encoding='utf-8')
局部解析
for div in div_list:
# 局部解析
title = div.xpath('./a/div[2]//h3/text()')[0]
money = div.xpath('./a/div[2]//section//p[5]/text()')
# print(money)
# print(title)
fp.write(title + '\n')
xpath爬取4k图片
导包:
import requests
from lxml import etree
import os
if __name__ == "__main__":
需要爬取的网址:
url = "https://pic.netbian.com/4kdongman/"
url1 = "https://pic.netbian.com/4kdongman/index_%d.html"
UA伪装:
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84"
}
请求数据(可能会出现乱码的情况,所以先要转码,然后获取text文件)
response = requests.get(url=url, headers=headers)
response.encoding = 'gbk'
page_text = response.text
数据解析:src的属性值 alt属性
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="slist"]//li')
创建一个文件夹:用于集中存放数据,因为网站的url在分页上有点奇怪,所以我就将第一页单独存储,后面在一起存放
if not os.path.exists('./picLibs'):
os.mkdir('./picLibs')
获取分页数据:
for li in li_list:
photo = "https://pic.netbian.com" + li.xpath('./a/img/@src')[0]
name = li.xpath('./a/img/@alt')[0] + '.jpg'
# print(name + " " + photo)
img_path = 'picLibs/' + name
img_data = requests.get(url=photo, headers=headers).content
with open(img_path, 'wb') as fp:
fp.write(img_data)
print(name + ' success')
for pageNum in range(2, 3):
new_url = format(url1 % pageNum)
response = requests.get(url=new_url, headers=headers)
response.encoding = 'gbk'
page_text = response.text
# 数据解析:src的属性值 alt属性
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="slist"]//li')
for li in li_list:
photo = "https://pic.netbian.com" + li.xpath('./a/img/@src')[0]
name = li.xpath('./a/img/@alt')[0] + '.jpg'
# print(name + " " + photo)
img_path = 'picLibs/' + name
img_data = requests.get(url=photo, headers=headers).content
with open(img_path, 'wb') as fp:
fp.write(img_data)
print(name + ' success')
爬取免费简历模板
import requests
from lxml import etree
import os
if __name__ == "__main__":
url = "https://sc.chinaz.com/jianli/free.html"
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84"
}
response = requests.get(url=url, headers=headers)
response.encoding = 'gbk'
page_text = response.text
# 数据解析
tree = etree.HTML(page_text)
a_list = tree.xpath('//div[@class="sc_warp mt20"]/div/div/div')
# print(a_list)
for a in a_list:
photo_url = "https:" + a.xpath('./a/@href')[0]
photo_name = a.xpath('./a/img/@alt')[0] + ".rar"
# print(photo_url)
response = requests.get(url=photo_url, headers=headers)
response.encoding = 'gbk'
page_text = response.text
data_tree = etree.HTML(page_text)
ul_list = data_tree.xpath('//div[@class="clearfix mt20 downlist"]/ul/li[1]/a/@href')[0]
print(ul_list)
http/https 协议特性:无状态。
没有请求到对应页面数据的原因:发起的第二次基于个人主页页面请求的时候,服务器端并不知道该此请求是基于登录状态下的请求。
cookie:用来让服务器端记录客户端的相关状态。
- 手动处理:通过抓包工具获取cookie值,将该值封装到headers中。(不建议)
- 自动处理:
- cookie值的来源是:模拟登录post请求后,由服务器端创建
- session会话对象的作用
- 1、可以进行请求的发送。
- 2、如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象
- 创建一个session对象:session = requests.Session()
- 使用session对象进行模拟登录post请求的发送(cookie就会被存储在session中)
- session对象对个人主页对应的get请求进行发送(携带了cookie)
代理
代理:破解封 IP 这种反爬机制。代理服务器
代理的作用:
- 突破自身的IP访问的限制
- 隐藏自身真实IP
代理 IP 的类型:
- HTTP:应用到 http 协议对应的 url 中
- HTTPS:应用到 https 协议对应的 url 中
import requests
url = 'https://www.baidu.com/s?wd=IP'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52'
}
response = requests.get(url=url, headers=headers, proxies={"http": "222.110.147.50:3128"}).text
print(response)
代理 IP 的匿名度:
- 透明:服务器知道该次请求使用了代理,也知道请求对应的真实 IP
- 匿名:知道使用了代理,不知道真实的 IP
- 高匿:不知道使用了代理,更不知道真实的 IP
反爬机制:官网对自己进行封 IP
反反爬策略:使用代理进行请求发送
高性能异步爬虫
首先要知道什么是协程、异步:
举个例子:假设有1个洗衣房,里面有10台洗衣机,有一个洗衣工在负责这10台洗衣机。那么洗衣房就相当于1个进程,洗衣工就相当1个线程。如果有10个洗衣工,就相当于10个线程,1个进程是可以开多线程的。这就是多线程!
那么协程呢?先不急。大家都知道,洗衣机洗衣服是需要等待时间的,如果10个洗衣工,1人负责1台洗衣机,这样效率肯定会提高,但是不觉得浪费资源吗?明明1 个人能做的事,却要10个人来做。只是把衣服放进去,打开开关,就没事做了,等衣服洗好再拿出来就可以了。就算很多人来洗衣服,1个人也足以应付了,开好第一台洗衣机,在等待的时候去开第二台洗衣机,再开第三台,……直到有衣服洗好了,就回来把衣服取出来,接着再取另一台的(哪台洗好先就取哪台,所以协程是无序的)。这就是计算机的协程!洗衣机就是执行的方法。
当你程序中方法需要等待时间的话,就可以用协程,效率高,消耗资源少。
好了!现在来总结一下:
洗衣房 ==> 进程
洗衣工 ==> 线程
洗衣机 ==> 方法(函数)
目的:在爬虫中使用异步实现高性能的数据爬取操作。
如果按照之前的想法对需求网站进行爬取的话;单个网站还算好,但3个?30个?300个?30000个?
可想而知,整个过程下来,效率得多低啊!
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52'
}
urls = [
'https://www.baidu.com/',
'https://sc.chinaz.com/jianli/free.html',
'https://bj.58.com/ershoufang/'
]
def get_content(url):
print('正在爬取: ', url)
# get方法是一个阻塞的方法
response = requests.get(url=url, headers=headers)
# 如果状态码没有问题,则可爬取
if response.status_code == 200:
return response.content
def parse_content(content):
print('响应数据的长度: ', len(content))
for url in urls:
content = get_content(url)
parse_content(content)
就类似上述代码,并未进行异步爬取,而进行的是单线程串行爬取。
异步爬虫的方式:
- 多线程,多进程(不建议使用):
- 好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行
- 弊端:无法无限制的开启多进程或者多线程。
- 线程池,进程池:
- 好处:我们可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销。
- 弊端:池中线程或进程的数量是上限的。
我们先模拟单线程串行的方式,得出整个过程的时间
import time
def get_page(str):
print('正在下载: ', str)
time.sleep(2)
print('下载成功: ', str)
name_list = ['xiaozi', 'aa', 'bb', 'cc']
start_time = time.time()
for i in range(len(name_list)):
get_page(name_list[i])
end_time = time.time()
print('%d second' % (end_time - start_time)) # 8 second
用线程池的形式去爬取
import time
# 导入线程池模块对应的类
from multiprocessing.dummy import Pool
# 使用线程池的方式执行
start_time = time.time()
def get_page(str):
print('正在下载: ', str)
time.sleep(2)
print('下载成功: ', str)
name_list = ['xiaozi', 'aa', 'bb', 'cc']
# 实例化一个线程池对象
pool = Pool(4)
# 将列表中每一个列表元素传递给get_page进行处理
pool.map(get_page, name_list)
end_time = time.time()
print(end_time - start_time) # 2 second
单线程 + 异步线程(推荐):
- event_loop: 事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。
- coroutine: 协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
- task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
- future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。
- async:定义一个协程
- await:用来挂起阻塞方法的执行。
import asyncio
async def request(url):
print('正在请求的url是:', url)
print('请求成功:', url)
# async修饰的函数, 调用之后返回的一个协程对象
c = request('www.baidu.com')
# 创建一个时间循环对象
loop = asyncio.get_event_loop()
# 将协程对象注册到loop中, 然后启动loop
loop.run_until_complete(c)
task的使用:
import asyncio
async def request(url):
print('正在请求的url是:', url)
print('请求成功:', url)
# async修饰的函数, 调用之后返回的一个协程对象
c = request('www.baidu.com')
# task的使用
loop = asyncio.get_event_loop()
# 基于loop创建了一个task对象
task = loop.create_task(c)
# 协程状态为未执行
print(task)
loop.run_until_complete(task)
# 协程状态为执行
print(task)
future的使用:
import asyncio
async def request(url):
print('正在请求的url是:', url)
print('请求成功:', url)
# async修饰的函数, 调用之后返回的一个协程对象
c = request('www.baidu.com')
# future的使用
loop = asyncio.get_event_loop()
# 需要封装一个协程对象
task = asyncio.ensure_future(c)
# task对象还未执行
print(task)
loop.run_until_complete(task)
# task对象已被执行
print(task)
task和future的区别就是:
- task是基于 时间循环对象(event_loop),future不用基于时间循环的创建
绑定回调的使用:
import asyncio
async def request(url):
print('正在请求的url是:', url)
print('请求成功:', url)
return url
# async修饰的函数, 调用之后返回的一个协程对象
c = request('www.baidu.com')
def callback_func(task):
# result返回的就是任务对象中封装的协程对象对应函数的返回值
print(task.result())
# 绑定回调
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(c)
# 将回调函数绑定到任务对象中
task.add_done_callback(callback_func)
loop.run_until_complete(task)
async\await 的使用:
正常的函数在执行时是不会中断的,所以你要写一个能够中断的函数,就需要添加async关键。
async 用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件(假设挂起条件是sleep(5))消失后,也就是5秒到了再回来执行。
await 用来用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。await 后面只能跟异步程序或有 await 属性的对象,因为异步程序与一般程序不同。假设有两个异步函数async a,async b,a中的某一步有await,当程序碰到关键字await b()后,异步程序挂起后去执行另一个异步b程序,就是从函数内部跳出去执行其他函数,当挂起条件消失后,不管b是否执行完,要马上从b程序中跳出来,回到原程序执行原来的操作。如果await后面跟的b函数不是异步函数,那么操作就只能等b执行完再返回,无法在b执行的过程中返回。如果要在b执行完才返回,也就不需要用await关键字了,直接调用b函数就行。所以这就需要await后面跟的是异步函数了。在一个异步函数中,可以不止一次挂起,也就是可以用多个await。
简述同步和异步的区别
同步的思想是:所有的操作都做完,才返回给用户。这样用户在线等待的时间太长,给用户一种卡死了的感觉(就是系统迁移中,点击了迁移,界面就不动了,但是程序还在执行,卡死了的感觉)。这种情况下,用户不能关闭界面,如果关闭了,即迁移程序就中断了。
import asyncio
import time
async def request(url):
print('正在下载', url)
time.sleep(2)
print('下载完毕', url)
start = time.time()
urls = [
'www.baidu.com',
'www.sogou.com',
'www.goubanjia.com'
]
# 任务列表: 存放多个任务对象
stasks = []
for url in urls:
c = request(url)
task = asyncio.ensure_future(c)
stasks.append(task)
loop = asyncio.get_event_loop()
# 需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(stasks))
print(time.time()-start)
异步的思想是:将用户请求放入消息队列,并反馈给用户,系统迁移程序已经启动,你可以关闭浏览器了。然后程序再慢慢地去写入数据库去。这就是异步。但是用户没有卡死的感觉,会告诉你,你的请求系统已经响应了。你可以关闭界面了。
import asyncio
import time
async def request(url):
print('正在下载', url)
# 异步协程中如果出现了同步模块相关的代码,那么就无法实现异步
# time.sleep(2)
# 当在asyncio中遇到阻塞操作必须进行手动挂起
await asyncio.sleep(2)
print('下载完毕', url)
start = time.time()
urls = [
'www.baidu.com',
'www.sogou.com',
'www.goubanjia.com'
]
# 任务列表: 存放多个任务对象
stasks = []
for url in urls:
c = request(url)
task = asyncio.ensure_future(c)
stasks.append(task)
loop = asyncio.get_event_loop()
# 需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(stasks))
print(time.time()-start)
同步和异步本身是相对的
同步就相当于是 当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待。用户使用起来会有不友好。
异步就是,当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。
存在就有其道理 异步虽然好 但是有些问题是要用同步用来解决,比如有些东西我们需要的是拿到返回的数据在进行操作的。这些是异步所无法解决的。
aiohttp+多任务异步协程实现异步爬虫:(完整代码)
- get请求、post请求
- (UA伪装)headers
- params(get)/data(post)
- (代理参数)proxy=‘http://ip:port’
import requests
import asyncio
import time
import aiohttp
start = time.time()
urls = [
'https://www.baidu.com/',
'https://www.baidu.com/',
'https://www.baidu.com/'
]
async def get_page(url):
async with aiohttp.ClientSession() as session:
async with await session.get(url) as response:
# text()返回字符串形式的响应数据
# read()返回二进制形式的响应数据
# json()返回的就是json对象
page_text = await response.text()
print(page_text)
# print('正在下载', url)
# # requests.get是基于同步,必须使用基于异步的网络请求模块进行指定url的请求发送
# # aiohttp: 基于异步网络请求的模块
# response = requests.get(url=url)
# print('下载完毕', response.text)
tasks = []
for url in urls:
c = get_page(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end = time.time() # 0.17054343223571777
print('总耗时: ', end-start)
当我们不使用异步爬取时:对于少量的网址和少量爬取内容可能在时间和效率上影响并不大,但是如果是网址数量和数据量过大则会影响时间和效率
async def get_page(url):
print('正在下载', url)
# requests.get是基于同步,必须使用基于异步的网络请求模块进行指定url的请求发送
# aiohttp: 基于异步网络请求的模块
response = requests.get(url=url)
print('下载完毕', response.text)
当使用异步爬取时:则多网址同时爬取,不仅节约了时间,提高了效率
async def get_page(url):
async with aiohttp.ClientSession() as session:
async with await session.get(url) as response:
# text()返回字符串形式的响应数据
# read()返回二进制形式的响应数据
# json()返回的就是json对象
page_text = await response.text()
print(page_text)
scrapy框架
scrapy框架的基本使用:
- 环境的安装:
pip install scrapy
- 创建一个工程:scrapy startproject xxxPro
- cd xxxPro
- 在spiders子目录中创建一个爬虫文件
- scrapy genspider spiderName www.xxx.com
- 执行工程:
- scrapy crawl spiderName
scrapy数据解析
scrapy持久化存储
- 基于终端指令:
- 要求:只可以将parse方法的返回值存储到本地的文本文件中
- 注意:持久化存储对应的文本文件类型只可以为:‘json’, ‘jsonlines’, ‘jl’, ‘csv’, ‘xml’, ‘marshal’, 'pickle’
- 指令:crapy crawl xxx -o filePath
- 优点:简洁、高效、便捷
- 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)
- 基于管道
- 编码流程:
- 数据解析
- 在item类中定义相关的属性
- 将解析的数据封装存储到item类型的对象中
- 将item类型的对象提交给管道进行持久化存储的操作
- 在管道类的process_item中要将其接收到的item对象中存储的数据进行持久化存储操作
- 在配置文件中开启管道
- 好处:
- 通用性强
- 缺点:
- 编码较为繁琐
- 编码流程:
- 面试题:将爬取到的数据一份存储到本地,一份存储到数据库,如何实现
- 管道文件中一个管道类对应的是将数据库存储到一种平台
- 爬虫文件提交的item只会给管道文件中第一个被执行的管道类接受
- process_item中的return item表示将item传递给下一个即将被执行的管道类
- 基于Spider的全站数据爬取
- 就是将网站中某板块下的全部页码对应的页面数据进行爬取
- 需求:爬取4K美女壁纸的名称
- 实现方式:
- 将所有页面的url添加到start_urls列表(不推荐)
- 自行手动进行请求发送(推荐)
- 手动请求发送
- yield scrapy.Request(url, callback):callback专门用作于数据解析
- 手动请求发送
- 五大核心组件
- 引擎(Scrapy)
- 用来处理整个系统的数据流处理, 触发事务(框架核心)
- 调度器(Scheduler)
- 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
- 下载器(Downloader)
- 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
- 爬虫(Spiders)
- 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
- 项目管道(Pipeline)
- 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
- 引擎(Scrapy)
- 图片数据爬取之ImagesPipeline
- 基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别
- 字符串:只需要基于xpath进行解析且提交管道进行持久化存储
- 图片:xpath解析出图片src的属性值。单独的对图片地址发起请求获取图片二进制类型的数据
- ImagesPipeline:
- 只需要将img的src的属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制类型的数据,且还会帮我们进行持久化存储
- 需求:爬取站长素材中的高清图片
- 使用流程:
- 数据解析(图片的地址)
- 将存储图片地址的item提交到指定的管道类
- 将管道文件中自定制一个基于ImagesPipeLine的一个管道类
- get_media_request()
- file_path()
- item_completed()
- 在配置文件中操作:
- 指定图片存储的目录:IMAGES_STORE = ‘./imgs bobo’
- 指定开启的管道:自定制的管道类
- 基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别
- 中间件:
- 下载中间件
- 位置:引擎和下载器之间
- 作用:批量拦截到整个工程中所有的请求和响应
- 拦截请求:
- UA伪装
- 代理IP
- 拦截响应:
- 篡改响应数据,响应对象
- 下载中间件
import scrapy
class FirstSpider(scrapy.Spider):
# 爬虫文件的名称: 就是爬虫源文件的一个唯一标识
name = 'first'
# 允许的域名: 用来限定start_urls列表中哪些url可以进行请求发送
allowed_domains = ['www.baidu.com']
# 起始的url列表:该列表中存放的url会被scrapy自动进行请求的发送
start_urls = ['https://www.baidu.com/',
'https://www.sogou.com/']
def parse(self, response):
pass
scrapy的简单爬取糗事百科信息
import scrapy
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
# 解析: 作者的名称+段子的内容
div_list = response.xpath('//div[@class="col1 old-style-col1"]/div')
for div in div_list:
# xpath返回的是列表, 但是列表元素一定是Selector类型对象
# extract可以将Selector对象中data参数存储的字符串提取出来
# author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
# 如果列表调用了extract之后,则表示将列表中每一个Selector对象中data对应的字符串提取出来
content = div.xpath('./a[1]/div/span//text()').extract()
content = ''.join(content)
print(author, content)
break
settings.py(关掉君子协议,加上UA伪装,可以选择不要日志信息)
BOT_NAME = 'qiubaiPro'
SPIDER_MODULES = ['qiubaiPro.spiders']
NEWSPIDER_MODULE = 'qiubaiPro.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
基于终端指令的持久化存储的方式
要注意存储文件的后缀(只能为 ‘json’, ‘jsonlines’, ‘jl’, ‘csv’, ‘xml’, ‘marshal’, ‘pickle’)
import scrapy
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
# 解析: 作者的名称+段子的内容
div_list = response.xpath('//div[@class="col1 old-style-col1"]/div')
all_data = [] # 存储所有
for div in div_list:
# xpath返回的是列表, 但是列表元素一定是Selector类型对象
# extract可以将Selector对象中data参数存储的字符串提取出来
# author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
# 如果列表调用了extract之后,则表示将列表中每一个Selector对象中data对应的字符串提取出来
content = div.xpath('./a[1]/div/span//text()').extract()
content = ''.join(content)
dic = {
'author': author,
'content': content
}
all_data.append(dic)
return all_data
基于管道持久化存储操作
同样我们还是爬取糗事百科,但是是基于管道的形式,过程较为繁琐
qiubai.py
import scrapy
from qiubaiPro.items import QiubaiproItem
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
# 解析: 作者的名称+段子的内容
div_list = response.xpath('//div[@class="col1 old-style-col1"]/div')
all_data = [] # 存储所有
for div in div_list:
# xpath返回的是列表, 但是列表元素一定是Selector类型对象
# extract可以将Selector对象中data参数存储的字符串提取出来
# author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
# 如果列表调用了extract之后,则表示将列表中每一个Selector对象中data对应的字符串提取出来
content = div.xpath('./a[1]/div/span//text()').extract()
content = ''.join(content)
item = QiubaiproItem()
item['author'] = author
item['content'] = content
yield item # 将item提交给了管道
items.py
import scrapy
class QiubaiproItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
author = scrapy.Field()
content = scrapy.Field()
# pass
pipelines.py
from itemadapter import ItemAdapter
class QiubaiproPipeline:
fp = None
# 重写父类的一个方法: 该方法只在开始爬虫的时候被调用一次
def open_spider(self, spider):
print('开始爬虫......')
self.fp = open('./qiubai.txt', 'w', encoding='utf-8')
# 专门用来处理item类型对象
# 该方法可以接收爬虫文件提交过来的item对象
# 该方法每接收到一个item就会被调用一次
def process_item(self, item, spider):
author = item['author']
content = item['content']
self.fp.write(author + ":" + content + '\n')
return item
def close_spider(self, spider):
print('结束爬虫....')
self.fp.close()
settings.py
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'qiubaiPro.pipelines.QiubaiproPipeline': 300,
# 300表示的是优先级,数值越小优先级越高
}
# Enable
基于Spider的全站数据爬取
因为这一次我们不是只爬取一页的数据,而是会分页爬取;所以特别重要的一点是递归思想:yield scrapy.Request(url=new_url, callback=self.parse)通过不断调用parse函数,爬取不同页的数据
meinv.py
import scrapy
class MeinvSpider(scrapy.Spider):
name = 'meinv'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://pic.netbian.com/4kmeinv/']
# 生成一个通用的url模板(不可变)
url = 'https://pic.netbian.com/4kmeinv/index_%d.html'
page_num = 2
def parse(self, response):
li_list = response.xpath('//div[2]/div/div[3]/ul/li')
for li in li_list:
img_name = li.xpath('./a/b/text()').extract_first()
print(img_name)
print('------------------')
if self.page_num <= 137:
new_url = format(self.url % self.page_num)
self.page_num += 1
# 手动请求发送: callback回调函数是专门用作于数据解析
yield scrapy.Request(url=new_url, callback=self.parse)
settings.py
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
爬取BOSS直聘数据
from selenium import webdriver
import csv
import time
key_word = input('输入你想要的岗位名称: ')
f = open(f'{key_word}data.csv', mode='a', encoding='utf-16', newline='')
csv_writer = csv.DictWriter(f, fieldnames=['标题', '地区', '公司名字', '薪资', '经验', '公司类型', '公司福利', '详情页'])
csv_writer.writeheader()
driver = webdriver.Chrome(executable_path='./chromedriver1.exe')
driver.get(f'https://www.zhipin.com/job_detail/?query={key_word}&city=101200100&industry=&position=')
driver.implicitly_wait(10)
# driver.find_element_by_css_selector('.ipt-search').send_keys('python')
# driver.find_element_by_css_selector('#wrap > div.column-search-panel.search-panel-new > div > div.search-box >
# div.search-form > form > button').click()
def get_job_info():
lis = driver.find_elements_by_css_selector('.job-list li')
# print(lis)
for li in lis:
area = li.find_element_by_css_selector('.job-area').text # 地区
title = li.find_element_by_css_selector('.job-name a').text # 标题
href = li.find_element_by_css_selector('.job-name a').get_attribute('href') # 详情页
company_name = li.find_element_by_css_selector('.company-text .name a').get_attribute('title').replace('招聘', '')
money = li.find_element_by_css_selector('.job-limit .red').text
exp = li.find_element_by_css_selector('.job-limit p').text
company_type = li.find_element_by_css_selector('.false-link').text
company_boon = li.find_element_by_css_selector('.info-desc').text
dit = {
'标题': title,
'地区': area,
'公司名字': company_name,
'薪资': money,
'经验': exp,
'公司类型': company_type,
'公司福利': company_boon,
'详情页': href,
}
# csv_writer.writerow(dit)
print(title, area, company_name, money, exp, company_type, company_boon, href, sep='|')
driver.find_element_by_css_selector('.next').click()
for page in range(10):
time.sleep(1)
get_job_info()
driver.quit()
基于scrapy爬取图片信息
img.py
import scrapy
from imgsPro.items import ImgsproItem
class ImgSpider(scrapy.Spider):
name = 'img'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://sc.chinaz.com/tupian/']
def parse(self, response):
div_list = response.xpath('//*[@id="container"]/div')
for div in div_list:
# 注意: 使用伪属性
src = 'https:' + div.xpath('./div/a/img/@src2').extract_first()
# print(src)
item = ImgsproItem()
item['src'] = src
yield item
items.py
import scrapy
class ImgsproItem(scrapy.Item):
src = scrapy.Field()
pipelines.py
import scrapy
from itemadapter import ItemAdapter
# class ImgsproPipeline:
# def process_item(self, item, spider):
# return item
from scrapy.pipelines.images import ImagesPipeline
class imgsPileLine(ImagesPipeline):
# 就是可以根据
def get_media_requests(self, item, info):
yield scrapy.Request(item['src'])
# 制定图片存储的路径
def file_path(self, request, response=None, info=None, *, item=None):
imgName = request.url.split('/')[-1]
return imgName
def item_completed(self, results, item, info):
return item # 返回给下一个即将被执行的管道类
settings.py
BOT_NAME = 'imgsPro'
SPIDER_MODULES = ['imgsPro.spiders']
NEWSPIDER_MODULE = 'imgsPro.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
ITEM_PIPELINES = {
'imgsPro.pipelines.imgsPileLine': 300,
}
MEDIA_ALLOW_REDIRECTS = True
# 指定图片存储的目录
IMAGES_STORE = './imgs bobo'
中间件 - 处理请求
middlewares.py
import random
class FirstbloodDownloaderMiddleware:
# UA池
user_agents_list = [
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60',
'Opera/8.0 (Windows NT 5.1; U; en)',
'Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50',
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50',
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
'Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2 ',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER',
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)',
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 SE 2.X MetaSr 1.0',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0) ',
]
# 代理IP
PROXY_http = [
'153.180.102.104:80',
'195.208.131.189:56055',
]
PROXY_https = [
'120.83.49.90:9000',
'95.189.112.214:35508',
]
# 拦截请求
def process_request(self, request, spider):
# UA伪装
request.headers['User-Agent'] = random.choice(self.user_agents_list)
# 为了验证代理的操作是否生效
request.meta['proxy'] = 'https://116.62.198.43:8080'
return None
# 拦截所有的响应
def process_response(self, request, response, spider):
# Called with the response returned from the downloader.
# Must either;
# - return a Response object
# - return a Request object
# - or raise IgnoreRequest
return response
# 拦截发生异常的请求
def process_exception(self, request, exception, spider):
if request.url.split(':')[0] == 'http':
# 代理
request.meta['proxy'] = 'http://' + random.choice(self.PROXY_http)
else:
request.meta['proxy'] = 'https://' + random.choice(self.PROXY_https)
return request # 将修正之后的请求对象进行重新的请求发送