前言
经过我们基础篇的学习,大家都对爬虫的基本理念有了深刻的了解,进阶篇主要是讲解重要的爬虫框架scrapy和selenium模块
scrapy框架介绍:
框架就是一个集成了很多功能并且具有很强通用性的一个项目模板。
scrapy框架:是爬虫中封装好的一个明星框架。
功能:高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式。
Scrapy5大组件
引擎(EGINE):大总管,负责控制数据的流向
调度器(SCHEDULER):由它来决定下一个要抓取的网址是什么,去重
下载器(DOWLOADER):用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
爬虫(SPIDERS):开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求request
项目管道(ITEM PIPLINES):在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
2大中间件
爬虫中间件:位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入和输出(用的很少)
下载中间件:引擎和下载器之间,加代理,加头,集成selenium
Scrapy框架的安装与配置
- wheel模块
pip install wheel
- lxml模块
pip install lxml
- PyOpenssl模块
pip install pyopenssl
- Twisted模块
pip install twisted
- pywin32模块
pip install pywin32
- scrapy模块
pip install scrapy
以上模块须按顺序安装,否则安装时可能会因缺少必要模块报错
由于pycharm不支持scrapy框架的一键生成,于是需要我们自己创建框架环境
在cmd下进入想要创建的位置输入scrapy startproject 创建的文件夹名称
进入创建的文件夹中运行scrapy genspider 名称 目标网址
生成py文件
随后用pycharm打开这个文件夹即可,jobbole.py
文件即scrapy genspider
命令生成,里面的变量名称为scrapy固定写法不可任意更改
目录介绍
firstscrapy # 项目名字
firstscrapy # 包
spiders # 所有的爬虫文件放在里面
baidu.py # 一个个的爬虫(以后基本上都在这写东西)
chouti.py
middlewares.py # 中间件(爬虫,下载中间件都写在这)
pipelines.py # 持久化相关写在这(items.py中类的对象)
main.py # 自己加的,执行爬虫
items.py # 一个一个的类,
settings.py # 配置文件
scrapy.cfg # 上线相关
Scrapy框架的使用
调试
```
main.py
import sys
import os
from scrapy.cmdline import execute
sys.path.append(os.path.dirname(os.path.abspath(file)))
execute(['scrapy','crawl','jobbole'])
```
由于scrapy爬虫需要cmd执行,python无法直接运行调试,于是我们写一个main.py
文件放到项目的根目录
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
是为了防止运行不会出错,将目录地址存到python变量环境中,execute要执行的命令,用列表的形式一个位置一段代码,等同于在cmd中执行scrapy crawl jobble
我们将pass打上断点,debugmain.py
文件,将鼠标移到response会发现已经访问成功200状态码和返回的网页源码文件,到此我们debug成功
抽屉新热榜实战
网址:https://dig.chouti.com/
新闻标题获取
审查元素发现标题在class包裹,使用bs4进行提取
```
import scrapy
from bs4 import BeautifulSoup
class ChoutiTestSpider(scrapy.Spider):
name = 'chouti_test'
allowed_domains = ['dig.chouti.com']
start_urls = ['http://dig.chouti.com/']
def parse(self, response):
soup=BeautifulSoup(response.text,'lxml')
divs=soup.find_all(class_='link-title')
for div in divs:
print(div.text)
```
标题和连接获取(extract和extract_first的区别)
```
import scrapy
from bs4 import BeautifulSoup
class ChoutiTestSpider(scrapy.Spider):
name = 'chouti_test'
allowed_domains = ['dig.chouti.com']
start_urls = ['http://dig.chouti.com/']
def parse(self, response):
div_list=response.xpath('//div[contains(@class,"link-item")]')
for div in div_list:
title = div.css('.link-title::text').extract()[0]
print(title)
url=div.css('.link-title::attr(href)').extract_first()
print(url)
```
extract取出来的是列表类型,所以用[0]取出值,extract_first可以直接把第一个值取出不需要下标extract()[0]=extract_first()
Scrapy持久化
所谓数据持久化,就是将数据保存到硬盘上让他一直存在
Settings.py文件是Scrapy项目的全局设置
其中ROBOTSTXT_OBEY
选项可以确定是否遵循爬虫协议,默认是TRUE,遇到robots的网站可以改为false
USER_AGENT选项默认是Scrapy,为了防止被拦截我们可以改成其他的
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
其中我们可以自己添加一个参数LOG_LEVEL='ERROR'
main函数如果不想要日志输出,可以用--nolog
from scrapy.cmdline import execute
execute(['scrapy','crawl','chouti_test','--nolog'])
但是这样就算有错误日志也不显示,于是用LOG_LEVEL='ERROR'
,有错显示,没错不显示
持久化方案一:parser必须返回列表套字典的形式,保存CSV格式
import scrapy
from bs4 import BeautifulSoup
class ChoutiTestSpider(scrapy.Spider):
name = 'chouti_test'
allowed_domains = ['dig.chouti.com']
start_urls = ['http://dig.chouti.com/']
def parse(self, response):
div_list=response.xpath('//div[contains(@class,"link-item")]')
data=[]
for div in div_list:
title = div.css('.link-title::text').extract()[0]
url=div.css('.link-title::attr(href)').extract_first()
data.append({'title': title, 'url': url})
return data
我们新建一个data列表,将每次的标题和url以字典的方式存储到,data里面
在命令行输入一下命令scrapy crawl chouti_test -o hhh.csv
即可生成一个hhh的csv文件
用excel打开
某些情况下用excel打开会出现乱码状态,可以用记事本打开csv文件
如果记事本中不是乱码,则另存为,如果当前是utf-8编码则转换为ansi,如果是ansi则换为utf-8即可
如果记事本打开也是乱码,则使用命令重新生成文件
方案二:高级,pipline item存储
首先在items文件写入一个类
import scrapy
class ChoutiItem(scrapy.Item):
title = scrapy.Field()
url=scrapy.Field()
调用这个类创建一个对象并传参,返回item(注意赋值时不可以用 . 的形式只能用[ ]这是scrapy框架内部决定的)
import scrapy
from chouti.items import ChoutiItem
class ChoutiTestSpider(scrapy.Spider):
name = 'chouti_test'
allowed_domains = ['dig.chouti.com']
start_urls = ['http://dig.chouti.com/']
def parse(self, response):
item=ChoutiItem()
div_list=response.xpath('//div[contains(@class,"link-item")]')
for div in div_list:
title = div.css('.link-title::text').extract()[0]
url=div.css('.link-title::attr(href)').extract_first()
item['title']=title
item['url']=url
return item
根据五大件原理图,当返回item时会将数据发送到pipline文件
但是如果我们直接运行他是不会显示的,需要设置settings下的ITEM_PIPELINES
,将他的注释解除,字典里可以添加和修改类以及类名,数字代表优先级,数字越小优先级越高,scrapy运行时只会调用ITEM_PIPELINES
中添加的类
piplines.py文件内容
import scrapy
class ChoutiItem(scrapy.Item):
title = scrapy.Field()
url=scrapy.Field()
当我们运行main,就会有返回值
但是只返回了一个,我们需要返回全部,于是将chouti_test文件中的return改为yield并且放到for循环中
import scrapy
from chouti.items import ChoutiItem
class ChoutiTestSpider(scrapy.Spider):
name = 'chouti_test'
allowed_domains = ['dig.chouti.com']
start_urls = ['http://dig.chouti.com/']
def parse(self, response):
item=ChoutiItem()
div_list=response.xpath('//div[contains(@class,"link-item")]')
for div in div_list:
title = div.css('.link-title::text').extract()[0]
url=div.css('.link-title::attr(href)').extract_first()
item['title']=title
item['url']=url
yield item
在pipline使用a模式存储文件
class ChoutiPipeline(object):
def process_item(self, item, spider):
with open('1.txt',mode='a',encoding='utf-8') as f:
f.write(item['title']+'\n')
f.write(item['url']+'\n')
那如何使用w模式存储文件呢?
我们知道,每次使用write都会覆盖原来文件的内容,于是我们需要引入scrapy的管道,我自己理解为scrapy的内置的函数,每种函数代表着不同的意义,并且定义出来就可以用
比如这次需要用到的open_spider
意义为开启执行爬虫只执行一次,close_spider
关闭爬虫执行时只执行一次,于是我们修改piplines
class ChoutiPipeline(object):
def open_spider(self, spider):
self.file=open('1.txt',mode='a',encoding='utf-8')
def process_item(self, item, spider):
self.file.write(item['title']+'\n')
self.file.write(item['url']+'\n')
def close_spider(self, spider):
self.file.close()
write的问题所在是每次句柄都会重新开启从开头进行写,用了管道可以发现file句柄自始至终只开启了一次,避免了重新写的问题
将数据写入到文件的同时写入数据库
之前说settings文件中的ITEM_PIPELINES
可以设置多个函数,我们在建一个数据库的函数
连接数据库需要pymysql库,pip install pymysql
下载一下就行
import pymysql
class MysqlPipeline(object):
def open_spider(self, spider):
self.conn = pymysql.connect(host='127.0.0.1', user='aaa', password="123456",
database='spider', port=3306)
def close_spider(self, spider):
self.conn.close()
def process_item(self, item, spider):
cursor = self.conn.cursor()
sql = 'insert into article (title,url)values(%s,%s) '
cursor.execute(sql, [item['title'], item['url']])
self.conn.commit()
return item
这里运行时候很有可能会报错
这是因为上面的ChoutiPipeline写文件时没有return导致下一个管道无法正常运行
运行结果:
使用scrapy爬取cnblogs整站文章
首先使用选择器获取文章标题和url,创建item对象保存
```
items.py
import scrapy
class CnblogsItem(scrapy.Item):
title=scrapy.Field()
url=scrapy.Field()
```
```
cnblog.py
import scrapy
from cnblogs.items import CnblogsItem
class CnblogSpider(scrapy.Spider):
name = 'cnblog'
allowed_domains = ['www.cnblogs.com']
start_urls = ['http://www.cnblogs.com/']
def parse(self, response):
div_list=response.css('article.post-item')
for div in div_list:
item = CnblogsItem()
title=div.xpath('.//div/a/text()').extract_first()
url=div.xpath('.//div/a/@href').extract_first()
item['title']=title
item['url']=url
```
但是要根据url访问页面来爬取,需要导入一个request来访问页面from scrapy import Request
,需要用到yield Request
中的callback参数来进行回调,如果callback为空默认回调parse函数,于是我们需要新建一个函数来进行回调
callback中的函数不可以有括号,否则会认识回调的是函数的返回结果
但是iteam是在parse函数中创建的,如果引入到parser_detail函数呢,这时候需要在Request引入meta元素,将item对象传入到字典中
这样的话,item就会存储三个元素,分别是title,url和内容content,当然需要在items文件中创建一个content
将ITEM_PIPELINES注释取消以便能访问pipelines文件
piplines连接数据库爬取即可
```
import pymysql
class CnblogsMysqlPipeline(object):
def open_spider(self,spider):
print('-------',spider.name)
self.conn=pymysql.connect( host='127.0.0.1', user='aaa', password="123456",database='spider', port=3306)
def process_item(self,item, spider):
cursor=self.conn.cursor()
sql='insert into article (title,url,content) values (%s,%s,%s)'
cursor.execute(sql,[item['title'],item['url'],item['content']])
self.conn.commit()
return item
def close_spider(self,spider):
self.conn.close()
````
那如果获取下一页的地址呢?
通过审查元素下一页按钮,使用css获取地址进行拼接,然后callback调用parse函数即可
next='https://www.cnblogs.com'+response.css('#paging_block>div a:last-child::attr(href)').extract_first()
yield Request(next)
如何提高爬虫效率
在配置文件中进行相关的配置即可:(默认还有一套setting)
1 增加并发:
默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
2 降低日志级别:
在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’
3 禁止cookie:
如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
4禁止重试:
对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
5 减少下载超时:
如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
selenium模块
定义:
selenuim是基于浏览器自动化的一个模块,最初是用来进行测试的,模拟人的行为,可以处理request无法执行javascript的情况
优点:
- 便捷的获取网站加载的动态数据
- 便捷实现模拟登陆
selenium的安装:
pip install selenium
selenium可以自动打开浏览器访问网页点击按钮等行为,支持许多主流浏览器,这里建议使用google浏览器
进入网址下载驱动http://chromedriver.storage.googleapis.com/index.html
查看自己的浏览器版本,选择一个小于等于浏览器版本的驱动即可
将其解压放到拥有环境变量的任意目录下,因为python需要调用驱动才能打开浏览器,因此需要一个环境变量
打开百度
from selenium import webdriver
bro=webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://www.baidu.com')
会看到上面写着受软件控制
无界面浏览器设置
from selenium.webdriver.chrome.options import Options
from selenium import webdriver
chrome_options = Options()
chrome_options.add_argument('window-size=1920x3000') #指定浏览器分辨率
chrome_options.add_argument('--disable-gpu') #谷歌文档提到需要加上这个属性来规避bug
chrome_options.add_argument('--hide-scrollbars') #隐藏滚动条, 应对一些特殊页面
chrome_options.add_argument('blink-settings=imagesEnabled=false') #不加载图片, 提升速度
chrome_options.add_argument('--headless') #浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败
可以根据自己的需求调整界面
浏览器设置前进后退
browser.back()
browser.forward()
元素交互
tag.send_keys() # 往里面写内容
tag.click() # 点击控件
tag.clear() # 清空控件内容
获取cookie
获取cookie
选项卡管理(了解)
```
from selenium import webdriver
import time
browser=webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')
print(browser.window_handles) #获取所有的选项卡
browser.switch_to_window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(2)
browser.switch_to_window(browser.window_handles[0])
browser.get('https://www.sina.com.cn')
browser.close()
```
异常处理
from selenium import webdriver
from selenium.common.exceptions import TimeoutException,NoSuchElementException,NoSuchFrameException
browser=webdriver.Chrome()
try:
browser.get('')
except Exception as e:
print(e)
finally:
#无论是否出异常,最终都要关掉
browser.close()
使用selenium登录百度
流程:打开网页——点击登录——输入账号密码——点击登录
使用bro.find_element(By.LINK_TEXT,'登录')
来搜索登录按钮,使用d_button.click()
点击按钮
审查元素查看输入框的id
find_element
来定位,定位成功send_keys
填入数据,随后定位登录按钮的位置,登录即可
```
from selenium import webdriver
import time
from selenium.webdriver.common.by import By
bro=webdriver.Chrome(executable_path='chromedriver.exe')
bro.implicitly_wait(5)
bro.get('https://www.baidu.com/')
d_button=bro.find_element(By.LINK_TEXT,'登录')
d_button.click()
bro.implicitly_wait(3)
username=bro.find_element(By.ID,'TANGRAM__PSP_11__userName')
username.send_keys('username')
password=bro.find_element(By.ID,'TANGRAM__PSP_11__password')
password.send_keys('password')
bro.implicitly_wait(3)
submit=bro.find_element(By.ID,'TANGRAM__PSP_11__submit')
submit.click()
time.sleep(10)
bro.close()
```
网上可能会看到find_element_by_id这种方法,但是在selenium4.0之后已经去掉这种方法了,import一个by文件使用by.来决定类型,其中implicitly_wait(5)
为隐式等待,如果网页超过五秒还没有加载出来则报错,如果加载出来了则继续执行
动作链
用selenium做自动化,有时候会遇到需要模拟鼠标操作才能进行的情况,比如单击、双击、点击鼠标右键、拖拽等等。而selenium给我们提供了一个类来处理这类事件——ActionChains
如果我们调用ActionChains动作是不会立即执行的,只有和perform一起用才会执行,也就是说所有的动作最后都要执行perform(),真正的去执行
例子:
driver.switch_to.frame('iframeResult')
sourse=driver.find_element_by_id('draggable')
target=driver.find_element_by_id('droppable')
ActionChains(driver).click_and_hold(sourse).perform()
ActionChains方法列表
click(on_element=None) ——单击鼠标左键
click_and_hold(on_element=None) ——点击鼠标左键,不松开
context_click(on_element=None) ——点击鼠标右键
double_click(on_element=None) ——双击鼠标左键
drag_and_drop(source, target) ——拖拽到某个元素然后松开
drag_and_drop_by_offset(source, xoffset, yoffset) ——拖拽到某个坐标然后松开
key_down(value, element=None) ——按下某个键盘上的键
key_up(value, element=None) ——松开某个键
move_by_offset(xoffset, yoffset) ——鼠标从当前位置移动到某个坐标
move_to_element(to_element) ——鼠标移动到某个元素
move_to_element_with_offset(to_element, xoffset, yoffset) ——移动到距某个元素(左上角坐标)多少距离的位置
perform() ——执行链中的所有动作
release(on_element=None) ——在某个元素位置松开鼠标左键
send_keys(*keys_to_send) ——发送某个键到当前焦点的元素
send_keys_to_element(element, *keys_to_send) ——发送某个键到指定元素
个人感觉用的并不多,了解一下即可
总结
本章是进阶篇,主要讲了scrapy框架的用法和selenium模块的使用方式,配合基础篇,希望大家能有所进步
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论