疫情期间在风变编程(https://www.pypypy.cn/#/)上学习了爬虫的相关知识,风变编程是一个交互式学习网站,目前开的模块还不是很多但是交互式在线教学实验的形式还是十分有趣,交互式的形式教一个读书顺序,督催一行一行读书,告诉什么时候应该动手,什么时候应该总结。
我们日常的数据来源可能通过爬虫、日志、业务数据库、智能硬件、第三方数据渠道、调研、实验、EXCEL采报等,爬虫作为数据获取的重要途经之一,作为数据分析人员也是必会的技能之一。
下面就网站的学习进行了简要的知识点记录。
Robots协议是互联网爬虫的一项公认的道德规范,在网站的域名后加上/robots.txt查看哪些页面是可以抓取的,哪些不可以
我们一般会选择用type()函数查看一下数据类型,因为Python是一门面向对象编程的语言,只有知道是什么对象,才能调用相关的对象属性和方法。
首先思考项目如何实现:
根据目标找方案,根据方案做执行,执行遇到问题就去学习、搜索
request
下载网页源码,文本,图片,音频
pip install requests
属性 | 作用 |
---|---|
response.status_code | 检查请求是否成功 |
response.content | 把response对象转换为二进制数据 |
response.text | 把response对象转换为字符串数据 |
response.encoding | 定义response对象的编码 |
响应状态码 | 说明 | 举例 | 说明 |
---|---|---|---|
1xx | 请求收到 | 100 | 继续提出请求 |
2xx | 请求成功 | 200 | 成功 |
3xx | 重定向 | 305 | 应使用代理访问 |
4xx | 客户端错误 | 403 | 禁止访问 |
5xx | 服务器端错误 | 503 | 服务不可用 |
它能把Response对象的内容以二进制数据的形式返回,适用于图片、音频、视频的下载
# 引入requests库
import requests
# 发出请求,并把返回的结果放在变量res中
res = requests.get('https://res.pandateacher.com/2018-12-18-10-43-07.png')
# 把Reponse对象的内容以二进制数据的形式返回
pic = res.content
# 新建了一个文件ppt.jpg,这里的文件没加路径,它会被保存在程序运行的当前目录下。
# 图片内容需要以二进制wb读写。你在学习open()函数时接触过它。
photo = open('ppt.jpg','wb')
# 获取pic的二进制内容
photo.write(pic)
# 关闭文件
photo.close()
可以把Response对象的内容以字符串的形式返回,适用于文字、网页源代码的下载。
res = requests.get('https://localprod.pandateacher.com/python-manuscript/crawler-html/sanguo.md')
#下载《三国演义》第一回,我们得到一个对象,它被命名为res
novel=res.text
#把Response对象的内容以字符串的形式返回
print(novel[:800])
#现在,可以打印小说了,但考虑到整章太长,只输出800字看看就好。在关于列表的知识那里,你学过[:800]的用法。
k = open('《三国演义》.txt','a+')
# 写进文件中
k.write(novel)
# 关闭文档
k.close()
pip install BeautifulSoup4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'html.parser') #把网页解析为BeautifulSoup对象
# 第1个参数用来标识解析器,我们要用的是一个Python内置库:html.parser。(它不是唯一的解析器,但是比较简单的)
虽然response.text和soup打印出的内容表面上看长得一模一样,却有着不同的内心,它们属于不同的类:
与 。前者是字符串,后者是已经被解析过的BeautifulSoup对象。之所以打印出来的是一样的文本,是因为BeautifulSoup对象在直接打印它的时候会调用该对象内的str方法,所以直接打印 bs 对象显示字符串是str的返回结果。
方法 | 作用 | 用法 | 示例 |
---|---|---|---|
find() | 提取满足要求的首个数据 | BeautifulSoup对象.find(标签,属性) | soup.find(‘div’,class_=‘books’) |
find_all() | 提取满足要求的所有数据 | BeautifulSoup对象.find_all(标签,属性) | soup.find_all(‘div’,class_=‘books’) |
soup = BeautifulSoup(res.text,'html.parser')
item = soup.find('div') #使用find()方法提取首个元素,并放到变量item里
- find_all
ResultSet对象,是Tag对象以列表结构储存了起来
# 返回的知识列表
items = soup.find_all('div')
print(type(items))
print(items)
如果只用其中一个参数就可以准确定位,就只用一个参数检索,标签,属性(类,ID)
用什么参数去查找和定位(在浏览器中查找,观察,相同的属性是提取数据的关键)
- 从列表中提取值(包含了HTML标签)
for item in items:
print('想找的数据都包含在这里了:\n',item) # 打印item
tag(从包含了HTML标签的数据中提取出需要的数据值)
- find(),find_all()
Tag对象可以使用find()与find_all()来继续检索
当我们在用text获取纯文本时,获取的是该标签内的所有纯文本信息,不论是直接在这个标签内,还是在它的子标签内
text可以这样做,但如果是要提取属性的值,是不可以的。父标签只能提取它自身的属性值,不能提取子标签的属性值
for item in items:
kind = item.find('h2') # 在列表中的每个元素里,匹配标签提取出数据
title = item.find(class_='title') #在列表中的每个元素里,匹配属性class_='title'提取出数据
brief = item.find(class_='info') #在列表中的每个元素里,匹配属性class_='info'提取出数据
print(kind,'\n',title,'\n',brief) # 打印提取出的数据
print(type(kind),type(title),type(brief)) # 打印提取出的数据类型
- Tag.text,Tag[‘属性名’]
Tag.text提出Tag对象中的文字,用Tag[‘href’]提取出URL
print(kind.text,'\n',title.text,'\n',title['href'],'\n',brief.text) # 打印书籍的类型、名字、链接和简介的文字
在实践操作当中,其实常常会因为标签选取不当,或者网页本身的编写没做好板块区分,你可能会多打印出一些奇怪的东西
一般有两种处理方案:数量太多而无规律,我们会换个标签提取;数量不多而有规律,我们会对提取的结果进行筛选——只要列表中的若干个元素就好
确定最小共同父级标签
也有一些网页,直接把所有的关键信息都放在第0个请求里,尤其是一些比较老(或比较轻量)的网站,我们用requests和BeautifulSoup就能解决
请求解析json数据
- XHR
Ajax技术(技术本身和爬虫关系不大,在此不做展开,你可以通过搜索了解)。应用这种技术,好处是显而易见的——更新网页内容,而不用重新加载整个网页。
这种技术在工作的时候,会创建一个XHR(或是Fetch)对象,然后利用XHR对象来实现,服务器和浏览器之间传输数据。 > 数据需要被有规律地组织起来,我们才能去查找、阅读、分析、理解。比如:汉语字典应该按照拼音排序,文件应该按照一定规律放进不同的文件夹,小说要有章节目录——大标题、中标题、小标题。
- 组织数据的规律
- 需要分层结构(文件夹、列表字典的嵌套)
- 同一层数据,要有排序(如汉语字典按拼音排列,如列表)
- 同一层数据,要有对应关系
不是所有的编程语言都能读懂Python里的数据类型(如,列表/字符串),但是所有的编程语言,都支持文本(比如在Python中,用字符串这种数据类型来表示文本)这种最朴素的数据类型。如此,json数据才能实现,跨平台,跨语言工作。
带参数请求数据
先把Network面板清空,再点击一下精彩评论的点击加载更多,看看有没有多出来的新XHR,多出来的那一个,就应该是和评论相关的啦
读懂参数,有两个重要的方法是“观察”和“比较”。“观察”指的是阅读参数的键与值,尝试理解它的含义。“比较”指的是比较两个相近的XHR——它们有哪些不同,对应的页面显示内容有什么不同。
#和?的功能是一样的,作用都是分隔,若把链接的#替换成?,访问的效果是一样的(注意:用?分隔的url不一定可以用#代替)。
查找XHR中的相关请求,查看headers/query string parametres
for i in range(5):
params = {
'g_tk':'5381',
'loginUin':'0',
'hostUin':'0',
'format':'json',
'inCharset':'utf8',
'outCharset':'GB2312',
'notice':'0',
'platform':'yqq.json',
'needNewCode':'0',
'cid':'205360772',
'reqtype':'2',
'biztype':'1',
'topid':'102065756',
'cmd':'6',
'needmusiccrit':'0',
'pagenum':str(i),
'pagesize':'15',
'lasthotcommentid':'song_102065756_3202544866_44059185',
'domain':'qq.com',
'ct':'24',
'cv':'10101010'
}
# 将参数封装为字典
res_comments = requests.get(url,params=params)
# 调用get方法,下载这个字典
json_comments = res_comments.json()
list_comments = json_comments['comment']['commentlist']
for comment in list_comments:
print(comment['rootcommentcontent'])
print('-----------------------------------')
Request Headers
我们把它称作请求头。它里面会有一些关于该请求的基本信息,比如:这个请求是从什么设备什么浏览器上发出?这个请求是从哪个页面跳转而来?
user-agent(中文:用户代理)会记录你电脑的信息和浏览器版本(如我的,就是windows10的64位操作系统,使用谷歌浏览器)。
origin(中文:源头)和referer(中文:引用来源)则记录了这个请求,最初的起源是来自哪个页面。它们的区别是referer会比origin携带的信息更多些。
import requests
url = 'https://c.y.qq.com/soso/fcgi-bin/client_search_cp'
headers = {
'origin':'https://y.qq.com',
# 请求来源,本案例中其实是不需要加这个参数的,只是为了演示
'referer':'https://y.qq.com/n/yqq/song/004Z8Ihr0JIu5s.html',
# 请求来源,携带的信息比“origin”更丰富,本案例中其实是不需要加这个参数的,只是为了演示
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
# 标记了请求从什么设备,什么浏览器上发出
}
# 伪装请求头
params = {
'ct':'24',
'qqmusic_ver': '1298',
'new_json':'1',
'remoteplace':'sizer.yqq.song_next',
'searchid':'64405487069162918',
't':'0',
'aggr':'1',
'cr':'1',
'catZhida':'1',
'lossless':'0',
'flag_qc':'0',
'p':1,
'n':'20',
'w':'周杰伦',
'g_tk':'5381',
'loginUin':'0',
'hostUin':'0',
'format':'json',
'inCharset':'utf8',
'outCharset':'utf-8',
'notice':'0',
'platform':'yqq.json',
'needNewCode':'0'
}
# 将参数封装为字典
res_music = requests.get(url,headers=headers,params=params)
存储数据
存储到excel
就是电子表格。它有专门保存文件的格式,即xls和xlsx(Excel2003版本的文件格式是xls,Excel2007及之后的版本的文件格式就是xlsx)。
pip install openpyxl
import openpyxl
#引用openpyxl 。
wb = openpyxl.Workbook()
#利用openpyxl.Workbook()函数创建新的workbook(工作薄)对象,就是创建新的空的Excel文件。
sheet = wb.active
#wb.active就是获取这个工作薄的活动表,通常就是第一个工作表。
sheet.title = 'new title'
#可以用.title给工作表重命名。现在第一个工作表的名称就会由原来默认的“sheet1”改为"new title"。
单元格填内容
sheet['A1'] = '漫威宇宙'
#把'漫威宇宙'赋值给第一个工作表的A1单元格,就是往A1的单元格中写入了'漫威宇宙'。
插入行
rows = [['美国队长','钢铁侠','蜘蛛侠'],['是','漫威','宇宙', '经典','人物']]
#先把要写入的多行内容写成列表,再放进大列表里,赋值给rows。
for i in rows:
sheet.append(i)
#遍历rows,同时把遍历的内容添加到表格里,这样就实现了多行写入。
print(rows)
#打印rows
EXCEL读取
wb = openpyxl.load_workbook('Marvel.xlsx')
sheet = wb['new title']
sheetname = wb.sheetnames
print(sheetname)
A1_cell = sheet['A1']
A1_value = A1_cell.value
print(A1_value)
既然我们要存储成Excel文件的话,我们得先添加表头,比如我们现在想存储歌曲名、所属专辑、播放时长和播放链接,那就可以先分别在A1、B1、C1、D1单元格中写入“歌曲名”、“所属专辑”、“播放时长”和“播放链接”。
csv
用csv格式存储数据,读写比较方便,易于实现,文件也会比Excel文件小。但csv文件缺少Excel文件本身的很多功能,比如不能嵌入图像和图表,不能生成公式。
csv文件里的逗号可以充当分隔同行字符串的作用。
import csv
#引用csv模块。
csv_file = open('demo.csv','w',newline='',encoding='utf-8')
#创建csv文件,我们要先调用open()函数,传入参数:文件名“demo.csv”、写入模式“w”、newline=''、encoding='utf-8'。
# 加newline=' '参数的原因是,可以避免csv文件出现两倍的行距(就是能避免表格的行与行之间出现空白行)。加encoding='utf-8',可以避免编码问题导致的报错或乱码。
writer = csv.writer(csv_file)
# 用csv.writer()函数创建一个writer对象。
r
r只读,指针在开头,文件不存在则报错
rb二进制只读,其余同左
r+独写,其余同左
rb+二进制独写,其余同左
w
w只写,文件不存在则新建,存在则覆盖
wb二进制只写,其余同左
w+独写,其余同左
wb+二进制独写,其余同左
a
a追加,文件指针放在末尾,文件不存在则新建
ab二进制追加,其余同左
a+追加且刻度,其余同左
ab+二进制追加,且可读,其余同左
创建新行
writer.writerow(['电影','豆瓣评分'])
#借助writerow()函数可以在csv文件里写入一行文字 "电影"和“豆瓣评分”。
读取csv
import csv
csv_file=open('demo.csv','r',newline='',encoding='utf-8')
reader=csv.reader(csv_file)
for row in reader:
print(row)
csv_file.close()
保存
wb.save('Marvel.xlsx')
#保存新建的Excel文件,并命名为“Marvel.xlsx”
进阶
post
post和get都可以带着参数请求,不过get请求的参数会在url上显示出来。
但post请求的参数就不会直接显示,而是隐藏起来。像账号密码这种私密的信息,就应该用post的请求。
通常,get请求会应用于获取网页数据,比如我们之前学的requests.get()。post请求则应用于向网页提交数据,比如提交表单类型数据(像账号密码就是网页表单的数据)
cookies(保持与浏览器长期联系)
实际很多情况下,由于网站的限制,不登录的话我们只能爬取到一小部分信息
一般当登录一个网站,你都会在登录页面看到一个可勾选的选项“记住我”,如果你勾选了,以后你再打开这个网站就会自动登录,这就是cookie在起作用
import requests
#引入requests。
url = ' https://wordpress-edu-3autumn.localprod.oc.forchange.cn/wp-login.php'
#把请求登录的网址赋值给url。
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}
#加请求头,前面有说过加请求头是为了模拟浏览器正常的访问,避免被反爬虫。
data = {
'log': 'spiderman', #写入账户
'pwd': 'crawler334566', #写入密码
'wp-submit': '登录',
'redirect_to': 'https://wordpress-edu-3autumn.localprod.oc.forchange.cn',
'testcookie': '1'
}
#把有关登录的参数封装成字典,赋值给data。
login_in = requests.post(url,headers=headers,data=data)
#用requests.post发起请求,放入参数:请求登录的网址、请求头和登录参数,然后赋值给login_in。
cookies = login_in.cookies
#提取cookies的方法:调用requests对象(login_in)的cookies属性获得登录的cookies,并赋值给变量cookies。
url_1 = 'https://wordpress-edu-3autumn.localprod.oc.forchange.cn/wp-comments-post.php'
#我们想要评论的文章网址。
data_1 = {
'comment': input('请输入你想要发表的评论:'),
'submit': '发表评论',
'comment_post_ID': '13',
'comment_parent': '0'
}
#把有关评论的参数封装成字典。
comment = requests.post(url_1,headers=headers,data=data_1,cookies=cookies)
#用requests.post发起发表评论的请求,放入参数:文章网址、headers、评论参数、cookies参数,赋值给comment。
#调用cookies的方法就是在post请求中传入cookies=cookies的参数。
print(comment.status_code)
#打印出comment的状态码,若状态码等于200,则证明我们评论成功。
登录生成了cookie,获取登录的cookie进行后面的操作
session
所谓的会话,你可以理解成我们用浏览器上网,到关闭浏览器的这一过程。session是会话过程中,服务器用来记录特定用户会话的信息。
session和cookies的关系还非常密切——cookies中存储着session的编码信息,session中又存储了cookies的信息
浏览器第一次访问购物网页时,服务器会返回set cookies的字段给浏览器,而浏览器会把cookies保存到本地
等浏览器第二次访问这个购物网页时,就会带着cookies去请求,而因为cookies里带有会话的编码信息,服务器立马就能辨认出这个用户,同时返回和这个用户相关的特定编码的session。
每次重新登录购物网站后,你之前在购物车放入的商品并不会消失的原因。因为你在登录时,服务器可以通过浏览器携带的cookies,找到保存了你购物车信息的session
import requests
#引用requests。
session = requests.session()
#用requests.session()创建session对象,相当于创建了一个特定的会话,帮我们自动保持了cookies。
url = 'https://wordpress-edu-3autumn.localprod.oc.forchange.cn/wp-login.php'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
}
data = {
'log':input('请输入账号:'), #用input函数填写账号和密码,这样代码更优雅,而不是直接把账号密码填上去。
'pwd':input('请输入密码:'),
'wp-submit':'登录',
'redirect_to':'https://wordpress-edu-3autumn.localprod.oc.forchange.cn',
'testcookie':'1'
}
session.post(url,headers=headers,data=data)
#在创建的session下用post发起登录请求,放入参数:请求登录的网址、请求头和登录参数。
url_1 = 'https://wordpress-edu-3autumn.localprod.oc.forchange.cn/wp-comments-post.php'
#把我们想要评论的文章网址赋值给url_1。
data_1 = {
'comment': input('请输入你想要发表的评论:'),
'submit': '发表评论',
'comment_post_ID': '13',
'comment_parent': '0'
}
#把有关评论的参数封装成字典。
comment = session.post(url_1,headers=headers,data=data_1)
#在创建的session下用post发起评论请求,放入参数:文章网址,请求头和评论参数,并赋值给comment。
print(comment)
#打印comment
session直接记录cookie
存储cookies
cookies能帮我们保存登录的状态,那我们就在第一次登录时把cookies存储下来,等下次登录再把存储的cookies读取出来,这样就不用重复输入账号密码了
cookie转字典再转json再转字符串
cookies_dict = requests.utils.dict_from_cookiejar(session.cookies)
#把cookies转化成字典。
print(cookies_dict)
#打印cookies_dict
cookies_str = json.dumps(cookies_dict)
#调用json模块的dumps函数,把cookies从字典再转成字符串。
print(cookies_str)
#打印cookies_str
f = open('cookies.txt', 'w')
#创建名为cookies.txt的文件,以写入模式写入内容。
f.write(cookies_str)
#把已经转成字符串的cookies写入文件。
f.close()
#关闭文件。
读取cookie
cookies_txt = open('cookies.txt', 'r')
#以reader读取模式,打开名为cookies.txt的文件。
cookies_dict = json.loads(cookies_txt.read())
#调用json模块的loads函数,把字符串转成字典。
cookies = requests.utils.cookiejar_from_dict(cookies_dict)
#把转成字典的cookies再转成cookies本来的格式。
session.cookies = cookies
#获取cookies:就是调用requests对象(session)的cookies属性。
cookie过期问题
加一个条件判断,如果cookies过期,就重新获取新的cookies
try:
session.cookies = cookies_read()
except FileNotFoundError:
sign_in()
session.cookies = cookies_read()
num = write_message()
if num.status_code == 200:
print('成功啦!')
else:
sign_in()
session.cookies = cookies_read()
num = write_message()
计算机之所以需要cookies和session,是因为HTTP协议是无状态的协议。
何为无状态?就是一旦浏览器和服务器之间的请求和响应完毕后,两者会立马断开连接,也就是恢复成无状态。
这样会导致:服务器永远无法辨认,也记不住用户的信息,像一条只有7秒记忆的金鱼。是cookies和session的出现,才破除了web发展史上的这个难题
cookies不仅仅能实现自动登录,因为它本身携带了session的编码信息,网站还能根据cookies,记录你的浏览足迹,从而知道你的偏好,只要再加以推荐算法
Selenium(控制浏览器)
有的网站登录很复杂,验证码难以破解,例如12306;有的网站页面交互复杂,所使用的技术难以被爬取,比如,腾讯文档;还有的网站,对URL的加密逻辑很复杂
selenium是一个强大的Python库,控制浏览器,做出自动打开、输入、点击等操作,就像是有一个真正的用户在操作一样。
静态网页:网页源代码中就包含着网页的所有信息
动态网页:数据不在HTML源代码中,而是在json中,需要找到json数据的真实URL,不论数据存在哪里,浏览器总是在向服务器发起各式各样的请求,当这些请求完成后,它们会一起组成开发者工具的Elements中所展示的,渲染完成的网页源代码。
在遇到页面交互复杂或是URL加密逻辑复杂的情况时,selenium就派上了用场,它可以真实地打开一个浏览器,等待所有数据都加载到Elements中之后,再把这个网页当做静态网页爬取
selenium文档:
https://selenium-python-zh.readthedocs.io/en/latest/
- 安装selenium包
pip install selenium # Windows电脑安装selenium
- 安装浏览器驱动
将下载好的chromedriver_win32.zip解压后是一个exe文件,将其复制到你的Python安装根目录
- 设置浏览器引擎
# 本地Chrome浏览器设置方法
from selenium import webdriver #从selenium库中调用webdriver模块
driver = webdriver.Chrome() # 设置引擎为Chrome,真实地打开一个Chrome浏览器
# driver是实例化的浏览器
- 获取数据
import time
# 本地Chrome浏览器设置方法
from selenium import webdriver #从selenium库中调用webdriver模块
driver = webdriver.Chrome() # 设置引擎为Chrome,真实地打开一个Chrome浏览器
driver.get('https://localprod.pandateacher.com/python-manuscript/hello-spiderman/') # 打开网页
time.sleep(1)
driver.close() # 关闭浏览器
- 解析提取数据
selenium所解析提取的,是Elements中的所有数据,而BeautifulSoup所解析的则只是Network中第0个请求的响应,用selenium把网页打开,所有信息就都加载到了Elements那里,之后,就可以把动态网页用静态网页的方法爬取
driver.get('https://localprod.pandateacher.com/python-manuscript/hello-spiderman/') # 访问页面
time.sleep(2) # 等待2秒
label = driver.find_element_by_tag_name('label') # 解析网页并提取第一个标签
print(label.text) # 打印label的文本
driver.close() # 关闭浏览器
方法
作用
find_element_by_tag_name
通过元素的标签名称选择
find_element_by_class_name
通过元素的class属性选择
find_element_by_id
通过元素的id选择
find_element_by_name
通过元素的name属性选择
find_element_by_link_text
通过链接文本获取超链接
find_element_by_partial_link_text
通过链接的部分文本获取超链接
# 以下方法都可以从网页中提取出'你好,蜘蛛侠!'这段文字
find_element_by_tag_name:通过元素的名称选择
# 如你好,蜘蛛侠!
# 可以使用find_element_by_tag_name('h1')
find_element_by_class_name:通过元素的class属性选择
# 如你好,蜘蛛侠!
# 可以使用find_element_by_class_name('title')
find_element_by_id:通过元素的id选择
# 如你好,蜘蛛侠!
# 可以使用find_element_by_id('title')
find_element_by_name:通过元素的name属性选择
# 如你好,蜘蛛侠!
# 可以使用find_element_by_name('hello')
#以下两个方法可以提取出超链接
find_element_by_link_text:通过链接文本获取超链接
# 如你好,蜘蛛侠!
# 可以使用find_element_by_link_text('你好,蜘蛛侠!')
find_element_by_partial_link_text:通过链接的部分文本获取超链接
# 如你好,蜘蛛侠!
# 可以使用find_element_by_partial_link_text('你好')
选出来的对象是webElement
WebElement
Tag
作用
WebElement.text
Tag.text
提取文字
WebElement.get_attribute()
Tag()
输入参数:属性名,可以提取属性值
driver.get('https://localprod.pandateacher.com/python-manuscript/hello-spiderman/') # 访问页面
time.sleep(2) # 等待两秒
label = driver.find_element_by_class_name('teacher') # 根据类名找到元素
print(type(label)) # 打印label的数据类型
print(label.get_attribute('type')) # 获取type这个属性的值
driver.close() # 关闭浏览器
选取多个数据
- 自动操作浏览器
.send_keys() # 模拟按键输入,自动填写表单
.click() # 点击元素
.clear()#清除元素
# 本地Chrome浏览器设置方法
from selenium import webdriver # 从selenium库中调用webdriver模块
import time # 调用time模块
driver = webdriver.Chrome() # 设置引擎为Chrome,真实地打开一个Chrome浏览器
driver.get('https://localprod.pandateacher.com/python-manuscript/hello-spiderman/') # 访问页面
time.sleep(2) # 暂停两秒,等待浏览器缓冲
teacher = driver.find_element_by_id('teacher') # 找到【请输入你喜欢的老师】下面的输入框位置
teacher.send_keys('必须是吴枫呀') # 输入文字
assistant = driver.find_element_by_name('assistant') # 找到【请输入你喜欢的助教】下面的输入框位置
assistant.send_keys('都喜欢') # 输入文字
button = driver.find_element_by_class_name('sub') # 找到【提交】按钮
button.click() # 点击【提交】按钮
time.sleep(1)
driver.close() # 关闭浏览器
真实地模拟人操作浏览器,需要等待网页缓冲的时间,在爬取大量数据的时候,速度会比较慢。
通常情况,在爬虫项目中,selenium都是用在其它方法无法解决,或是很难解决的问题时,才会用到。
发送邮件
链接服务器,通过账号和密码登录邮箱,填写收件人,填写主题,撰写正文,发送邮件,推出邮箱
- 链接服务器
首先查找smtp服务器
import smtplib
#smtplib是python的一个内置库,所以不需要用pip安装
mailhost='smtp.qq.com'
#把qq邮箱的服务器地址赋值到变量mailhost上,地址需要是字符串的格式。
qqmail = smtplib.SMTP()
#实例化一个smtplib模块里的SMTP类的对象,这样就可以使用SMTP对象的方法和属性了
qqmail.connect(mailhost,25)
#连接服务器,第一个参数是服务器地址,第二个参数是SMTP端口号。
- 邮箱填写
密码是smtp授权码
account = input('请输入你的邮箱:')
#获取邮箱账号
password = input('请输入你的密码:')
#获取邮箱密码
qqmail.login(account,password)
#登录邮箱,第一个参数为邮箱账号,第二个参数为邮箱密码
receiver=input('请输入收件人的邮箱:')
- 填写主题
from email.mime.text import MIMEText
from email.header import Header
#引入Header和MIMEText模块
content=input('请输入邮件正文:')
#输入你的邮件正文
message = MIMEText(content, 'plain', 'utf-8')
#实例化一个MIMEText邮件对象,该对象需要写进三个参数,分别是邮件正文,文本格式和编码.
subject = input('请输入你的邮件主题:')
#用input()获取邮件主题
message['Subject'] = Header(subject, 'utf-8')
#在等号的右边,是实例化了一个Header邮件头对象,该对象需要写入两个参数,分别是邮件主题和编码,然后赋值给等号左边的变量message['Subject']。
qqmail.sendmail(account, receiver, message.as_string())
#发送邮件,调用了sendmail()方法,写入三个参数,分别是发件人,收件人,和字符串格式的正文。
qqmail.quit()
#退出邮箱
爬虫定时工作
pip install schedule
import schedule
import time
#引入schedule和time
def job():
print("I'm working...")
#定义一个叫job的函数,函数的功能是打印'I'm working...'
schedule.every(10).minutes.do(job) #部署每10分钟执行一次job()函数的任务
schedule.every().hour.do(job) #部署每×小时执行一次job()函数的任务
schedule.every().day.at("10:30").do(job) #部署在每天的10:30执行job()函数的任务
schedule.every().monday.do(job) #部署每个星期一执行job()函数的任务
schedule.every().wednesday.at("13:15").do(job)#部署每周三的13:15执行函数的任务
while True:
schedule.run_pending()
time.sleep(1)
#13-15都是检查部署的情况,如果任务准备就绪,就开始执行任务。
框架
批量数据采集:协程
爬虫每发起一个请求,都要等服务器返回响应后,才会执行下一步。而很多时候,由于网络不稳定,加上服务器自身也需要响应的时间,导致爬虫会浪费大量时间在等待上。这也是爬取大量数据时,爬虫的速度会比较慢的原因
queue
- 用queue()创建队列
- 用put_nowait()存储数据
- 用get_nowait()提取数据
导入模块
from gevent import monkey
#从gevent库里导入monkey模块。
monkey.patch_all()
#monkey.patch_all()能把程序变成协作式运行,就是可以帮助程序实现异步。
import gevent,time,requests
#导入gevent、time、requests
from gevent.queue import Queue
#从gevent库里导入queue模块
是如何创建队列,以及怎么把任务存储进队列里。
start = time.time()
#记录程序开始时间
url_list = ['https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/']
work = Queue()
#创建队列对象,并赋值给work。
for url in url_list:
#遍历url_list
work.put_nowait(url)
#用put_nowait()函数可以把网址都放进队列里。
定义爬取函数,和如何从队列里提取出刚刚存储进去的网址。
def crawler():
while not work.empty():
#当队列不是空的时候,就执行下面的程序。
url = work.get_nowait()
#用get_nowait()函数可以把队列里的网址都取出。
r = requests.get(url)
#用requests.get()函数抓取网址。
print(url,work.qsize(),r.status_code)
#打印网址、队列长度、抓取请求的状态码。
让爬虫用多协程执行任务,爬取队列里的8个网站的代码(重点看有注释的代码)。
def crawler():
while not work.empty():
url = work.get_nowait()
r = requests.get(url)
print(url,work.qsize(),r.status_code)
tasks_list = [ ]
#创建空的任务列表
for x in range(2):
#相当于创建了2个爬虫
task = gevent.spawn(crawler)
#用gevent.spawn()函数创建执行crawler()函数的任务。
tasks_list.append(task)
#往任务列表添加任务。
gevent.joinall(tasks_list)
#用gevent.joinall方法,执行任务列表里的所有任务,就是让爬虫开始爬取网站。
end = time.time()
print(end-start)
超大型的爬虫程序,它除了靠多协程,一定还会靠多进程,甚至是分布式爬虫。
scrapy
- ScrapyEngin下面分Scheduler、Downloader、Spiders、Item Pipeline
- Scrapy Engine(引擎)就是这家爬虫公司的大boss,负责统筹公司的4大部门,每个部门都只听从它的命令,并只向它汇报工作。
- Scheduler(调度器)部门主要负责处理引擎发送过来的requests对象(即网页请求的相关信息集合,包括params,data,cookies,request headers…等),会把请求的url以有序的方式排列成队,并等待引擎来提取(功能上类似于gevent库的queue模块)。
- Downloader(下载器)部门则是负责处理引擎发送过来的requests,进行网页爬取,并将返回的response(爬取到的内容)交给引擎。它对应的是爬虫流程【获取数据】这一步。
- Spiders(爬虫)部门是公司的核心业务部门,主要任务是创建requests对象和接受引擎发送过来的response(Downloader部门爬取到的内容),从中解析并提取出有用的数据。它对应的是爬虫流程【解析数据】和【提取数据】这两步。
- Item Pipeline(数据管道)部门则是公司的数据部门,只负责存储和处理Spiders部门提取到的有用数据。这个对应的是爬虫流程【存储数据】这一步。
- Downloader Middlewares(下载中间件)的工作相当于下载器部门的秘书,比如会提前对引擎大boss发送的诸多requests做出处理。
- Spider Middlewares(爬虫中间件)的工作则相当于爬虫部门的秘书,比如会提前接收并处理引擎大boss发送来的response,过滤掉一些重复无用的东西。
- 在Scrapy里,整个爬虫程序的流程都不需要我们去操心,且Scrapy中的程序全部都是异步模式,所有的请求或返回的响应都由引擎自动分配去处理。
- 哪怕有某个请求出现异常,程序也会做异常处理,跳过报错的请求,继续往下运行程序。
Scrapy用法
- 创建Scrapy项目
- 定义Item(数量)
- 创建和编写spiders文件
- 修改settings.py文件
- 运行Scrapy爬虫
pip install scrapy
scrapy startproject 项目名
在spiders文件夹里创建爬虫文件
import scrapy
import bs4
class DoubanSpider(scrapy.Spider):
name = 'douban'#是定义爬虫的名字,这个名字是爬虫的唯一标识,等会启动爬虫的时候,要用到这个名字
allowed_domains = ['book.douban.com']#定义允许爬虫爬取的网址域名(不需要加https://)。如果网址的域名不在这个列表里,就会被过滤掉,不会跳转到某个奇怪的广告页面
start_urls = ['https://book.douban.com/top250?start=0']
def parse(self, response):
print(response.text)
定义数据
当我们每一次,要记录数据的时候,比如前面在每一个最小循环里,都要记录“书名”,“出版信息”,“评分”。我们会实例化一个对象,利用这个对象来记录数据
当数据完成记录,它会离开spiders,来到Scrapy Engine(引擎),引擎将它送入Item Pipeline(数据管道)处理
所创建的类将直接继承scrapy中的scrapy.Item类。这样,有许多好用属性和方法,就能够直接使用。
import scrapy
#导入scrapy
class DoubanItem(scrapy.Item):
#定义一个类DoubanItem,它继承自scrapy.Item
title = scrapy.Field()
#定义书名的数据属性
publish = scrapy.Field()
#定义出版信息的数据属性
score = scrapy.Field()
#定义评分的数据属性
scrapy.Field()这行代码实现的是,让数据能以类似字典的形式记录,它的数据类型是我们定义的DoubanItem,属于“自定义的Python字典”
设置
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
存储
csv
FEED_URI='./storage/data/%(name)s.csv'
FEED_FORMAT='CSV'
FEED_EXPORT_ENCODING='ansi'
excel
pip install openpyxl --upgrade
#需要修改`ITEM_PIPELINES`的设置代码:
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
# 'jobui.pipelines.JobuiPipeline': 300,
# }
import openpyxl
class JobuiPipeline(object):
#定义一个JobuiPipeline类,负责处理item
def __init__(self):
#初始化函数 当类实例化时这个方法会自启动
self.wb =openpyxl.Workbook()
#创建工作薄
self.ws = self.wb.active
#定位活动表
self.ws.append(['公司', '职位', '地址', '招聘信息'])
#用append函数往表格添加表头
def process_item(self, item, spider):
#process_item是默认的处理item的方法,就像parse是默认处理response的方法
line = [item['company'], item['position'], item['address'], item['detail']]
#把公司名称、职位名称、工作地点和招聘要求都写成列表的形式,赋值给line
self.ws.append(line)
#用append函数把公司名称、职位名称、工作地点和招聘要求的数据都添加进表格
return item
#将item丢回给引擎,如果后面还有这个item需要经过的itempipeline,引擎会自己调度
def close_spider(self, spider):
#close_spider是当爬虫结束运行时,这个方法就会执行
self.wb.save('./jobui.xlsx')
#保存文件
self.wb.close()
#关闭文件
运行
scrapy crawl douban
修改设置
DOWNLOAD_DELAY = 0
- Doraengineer’s blog说明