python爬虫由浅入深(进阶篇)

admin 2022年10月17日00:43:27python爬虫由浅入深(进阶篇)已关闭评论55 views字数 11844阅读39分28秒阅读模式

前言

经过我们基础篇的学习,大家都对爬虫的基本理念有了深刻的了解,进阶篇主要是讲解重要的爬虫框架scrapy和selenium模块

scrapy框架介绍:

框架就是一个集成了很多功能并且具有很强通用性的一个项目模板。

scrapy框架:是爬虫中封装好的一个明星框架。

功能:高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式。

Scrapy5大组件

引擎(EGINE):大总管,负责控制数据的流向
调度器(SCHEDULER):由它来决定下一个要抓取的网址是什么,去重
下载器(DOWLOADER):用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
爬虫(SPIDERS):开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求request
项目管道(ITEM PIPLINES):在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作

2大中间件

爬虫中间件:位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入和输出(用的很少)
下载中间件:引擎和下载器之间,加代理,加头,集成selenium

python爬虫由浅入深(进阶篇)

Scrapy框架的安装与配置

  1. wheel模块 pip install wheel
  2. lxml模块 pip install lxml
  3. PyOpenssl模块 pip install pyopenssl
  4. Twisted模块 pip install twisted
  5. pywin32模块 pip install pywin32
  6. scrapy模块 pip install scrapy

以上模块须按顺序安装,否则安装时可能会因缺少必要模块报错

由于pycharm不支持scrapy框架的一键生成,于是需要我们自己创建框架环境

在cmd下进入想要创建的位置输入scrapy startproject 创建的文件夹名称

进入创建的文件夹中运行scrapy genspider 名称 目标网址生成py文件

python爬虫由浅入深(进阶篇)

随后用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

python爬虫由浅入深(进阶篇)

我们将pass打上断点,debugmain.py文件,将鼠标移到response会发现已经访问成功200状态码和返回的网页源码文件,到此我们debug成功

抽屉新热榜实战

网址:https://dig.chouti.com/

新闻标题获取

python爬虫由浅入深(进阶篇)

审查元素发现标题在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)

```

python爬虫由浅入深(进阶篇)

标题和连接获取(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)

```

python爬虫由浅入深(进阶篇)

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文件

python爬虫由浅入深(进阶篇)

用excel打开

python爬虫由浅入深(进阶篇)

某些情况下用excel打开会出现乱码状态,可以用记事本打开csv文件

python爬虫由浅入深(进阶篇)

如果记事本中不是乱码,则另存为,如果当前是utf-8编码则转换为ansi,如果是ansi则换为utf-8即可

python爬虫由浅入深(进阶篇)

如果记事本打开也是乱码,则使用命令重新生成文件

方案二:高级,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文件

python爬虫由浅入深(进阶篇)

但是如果我们直接运行他是不会显示的,需要设置settings下的ITEM_PIPELINES,将他的注释解除,字典里可以添加和修改类以及类名,数字代表优先级,数字越小优先级越高,scrapy运行时只会调用ITEM_PIPELINES中添加的类

python爬虫由浅入深(进阶篇)

piplines.py文件内容

import scrapy
class ChoutiItem(scrapy.Item):
title = scrapy.Field()
url=scrapy.Field()

当我们运行main,就会有返回值

python爬虫由浅入深(进阶篇)

但是只返回了一个,我们需要返回全部,于是将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')

python爬虫由浅入深(进阶篇)

那如何使用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可以设置多个函数,我们在建一个数据库的函数

python爬虫由浅入深(进阶篇)

连接数据库需要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

这里运行时候很有可能会报错

python爬虫由浅入深(进阶篇)

这是因为上面的ChoutiPipeline写文件时没有return导致下一个管道无法正常运行

运行结果:

python爬虫由浅入深(进阶篇)

使用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函数,于是我们需要新建一个函数来进行回调

python爬虫由浅入深(进阶篇)

callback中的函数不可以有括号,否则会认识回调的是函数的返回结果

python爬虫由浅入深(进阶篇)

但是iteam是在parse函数中创建的,如果引入到parser_detail函数呢,这时候需要在Request引入meta元素,将item对象传入到字典中

python爬虫由浅入深(进阶篇)

这样的话,item就会存储三个元素,分别是title,url和内容content,当然需要在items文件中创建一个content

将ITEM_PIPELINES注释取消以便能访问pipelines文件

python爬虫由浅入深(进阶篇)

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()

````

那如果获取下一页的地址呢?

python爬虫由浅入深(进阶篇)

通过审查元素下一页按钮,使用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爬虫由浅入深(进阶篇)

将其解压放到拥有环境变量的任意目录下,因为python需要调用驱动才能打开浏览器,因此需要一个环境变量

打开百度

from selenium import webdriver
bro=webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://www.baidu.com')

python爬虫由浅入深(进阶篇)

会看到上面写着受软件控制

无界面浏览器设置

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

python爬虫由浅入深(进阶篇)

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模块的使用方式,配合基础篇,希望大家能有所进步

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月17日00:43:27
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   python爬虫由浅入深(进阶篇)http://cn-sec.com/archives/1353425.html