赞
踩
想要下载PDF或者md格式的笔记请点击以下链接获取
python爬虫学习笔记点击我获取
Scrapy+selenium详细学习笔记点我获取
Python超详细的学习笔记共21万字点我获取
## 安装:
pip install selenium
## 它与其他库不同的地方是他要启动你电脑上的浏览器, 这就需要一个驱动程序来辅助.
## 这里推荐用chrome浏览器
## chrome驱动地址:
http://chromedriver.storage.googleapis.com/index.html
https://googlechromelabs.github.io/chrome-for-testing/#stable
## 先查看自己谷歌浏览器的版本,我的是120.0.6099.255
然后打开这个驱动地址
https://googlechromelabs.github.io/chrome-for-testing/#stable
选stable 稳定版
然后在网页上搜索我们的版本,只要前三个部分对应上就行,也就是120.0.6099
如下图,就这样我们找到了我们想要的版本
把URL地址复制下载去浏览器下载驱动
下完以后解压,发现里面是个exe文件(往后浏览器更新了,驱动也需要重新下载对应版本的)
然后关键的来了. 把你下载的浏览器驱动放在python解释器所在的文件夹
Windwos: py -0p 查看Python路径
Mac: open + 路径
到此为止配置就结束了
from selenium.webdriver import Chrome # 导入谷歌浏览器的类
# 创建浏览器对象
web = Chrome() # 如果你的浏览器驱动放在了解释器文件夹
web.get("http://www.baidu.com") # 输入网址
selenium通过控制浏览器,所以对应的获取的数据都是elements中的内容
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
# 访问百度
driver.get("http://www.baidu.com/")
# 截图
driver.save_screenshot("baidu.png")
# 搜索关键字 杜卡迪
driver.find_element(By.ID, "kw").send_keys("杜卡迪")
# 点击id为su的搜索按钮
driver.find_element(By.ID, "su").click()
driver.page_source # 获取页面内容
driver.get_cookies()
driver.current_url
driver.close() # 退出当前页面
driver.quit() # 退出浏览器
1. selenium的导包: from selenium import webdriver 2. selenium创建driver对象: driver = webdriver.Chrome() 3. selenium请求数据: driver.get("http://www.baidu.com/") 4. selenium查看数据: driver.page_source 5. 关闭浏览器: driver.quit() 6. 根据id定位元素: driver.find_element_by_id("kw")/driver.find_element(By.ID, "kw") 7. 操作点击事件: click() 8. 给输入框赋值: send_keys() 9,获取cookie: driver.get_cookies() 10,刷新页面 driver.refresh() 11,执行js代码 driver.execute_script(f'window.scrollBy(0, {step_length})')
找到搜索框,输入内容,找到搜索按钮进行点击
import time from selenium.webdriver import Chrome # 导入谷歌浏览器的类 # 创建浏览器对象 from selenium.webdriver.common.by import By web = Chrome() # 如果你的浏览器驱动放在了解释器文件夹 web.get("https://www.gushiwen.cn/") # 输入网址 # 查找搜索框 txtKey = web.find_element(By.ID,'txtKey') txtKey.send_keys('唐诗') # 找到点击按钮 search = web.find_element(By.XPATH,'//*[@id="search"]/form/input[3]') search.click() print(search) time.sleep(5) web.quit()
import time from selenium.webdriver import Chrome from selenium.webdriver.common.by import By driver = Chrome() # 访问的网址 driver.get('https://www.gushiwen.cn/') """ 1 点击我的 到登录页面 2 获取账号节点 输入值 3 获取密码节点 输入值 4 获取验证码节点 输入值 5 点击登录 """ # 点我的 driver.find_element(By.XPATH, '/html/body/div[1]/div[1]/div/div[2]/div/a[6]').click() # 获取账号节点 email = driver.find_element(By.ID, 'email') email.send_keys('793390457@qq.com') # 获取密码节点 password = driver.find_element(By.ID, 'pwd') password.send_keys('xlg17346570232') # 获取验证码节点 yzm = driver.find_element(By.ID, 'code') yzm.send_keys('1234') time.sleep(5) # 点击登录 driver.find_element(By.ID, 'denglu').click() time.sleep(5)
import base64 import json import requests import time from selenium.webdriver import Chrome from selenium.webdriver.common.by import By def base64_api(uname, pwd, img, typeid): with open(img, 'rb') as f: base64_data = base64.b64encode(f.read()) b64 = base64_data.decode() data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64} result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text) if result['success']: return result["data"]["result"] else: #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别 return result["message"] return "" if __name__ == "__main__": driver = Chrome() # 访问的网址 driver.get('https://www.gushiwen.cn/') """ 1 点击我的 到登录页面 2 获取账号节点 输入值 3 获取密码节点 输入值 4 获取验证码节点 输入值 5 点击登录 """ # 点我的 driver.find_element(By.XPATH, '/html/body/div[1]/div[1]/div/div[2]/div/a[6]').click() # 获取账号节点 email = driver.find_element(By.ID, 'email') email.send_keys('793390457@qq.com') # 获取密码节点 password = driver.find_element(By.ID, 'pwd') password.send_keys('xlg17346570232') # 验证码图片的节点 img_path = "yzm.jpg" # screenshot截图并保存保存 driver.find_element(By.ID, 'imgCode').screenshot(img_path) # 识别验证码 result = base64_api(uname='luckyboyxlg', pwd='17346570232', img=img_path, typeid=3) print(result) # 获取验证码节点 yzm = driver.find_element(By.ID, 'code') yzm.send_keys(result) # 输入识别后的值 time.sleep(8) # 点击登录 driver.find_element(By.ID, 'denglu').click() time.sleep(50)
import base64 import json import requests import time from selenium.webdriver import Chrome from selenium.webdriver.common.by import By def base64_api(uname, pwd, img, typeid): with open(img, 'rb') as f: base64_data = base64.b64encode(f.read()) b64 = base64_data.decode() data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64} result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text) if result['success']: return result["data"]["result"] else: #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别 return result["message"] return "" if __name__ == "__main__": driver = Chrome() # 访问的网址 driver.get('https://www.gushiwen.cn/') """ 1 点击我的 到登录页面 2 获取账号节点 输入值 3 获取密码节点 输入值 4 获取验证码节点 输入值 5 点击登录 """ # 点我的 driver.find_element(By.XPATH, '/html/body/div[1]/div[1]/div/div[2]/div/a[6]').click() # 获取账号节点 email = driver.find_element(By.ID, 'email') email.send_keys('793390457@qq.com') # 获取密码节点 password = driver.find_element(By.ID, 'pwd') password.send_keys('xlg17346570232') # 验证码图片的节点 img_path = "yzm.jpg" # screenshot截图并保存保存 driver.find_element(By.ID, 'imgCode').screenshot(img_path) # 识别验证码 result = base64_api(uname='luckyboyxlg', pwd='17346570232', img=img_path, typeid=3) print(result) # 获取验证码节点 yzm = driver.find_element(By.ID, 'code') yzm.send_keys(result) # 输入识别后的值 time.sleep(8) # 点击登录 driver.find_element(By.ID, 'denglu').click() time.sleep(4) # 获取cookie保存到本地 cookies = driver.get_cookies() print(cookies) with open('cookies.txt', 'w', encoding='UTF-8') as f: f.write(json.dumps(cookies))
import time from selenium.webdriver.common.by import By from selenium.webdriver import Chrome import json driver = Chrome() # 访问登录 driver.get('https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx') # 本地cookie加载 with open('cookies.txt', 'r', encoding='UTF-8') as f: cookies = json.loads(f.read()) # cookie加载到selenium中 for cookie in cookies: driver.add_cookie(cookie) # 刷新一下 driver.refresh() driver.get('https://so.gushiwen.cn/user/collect.aspx') time.sleep(10)
import time from selenium.webdriver import Chrome from selenium.webdriver.common.by import By def window_scroll(driver, stop_length, step_length): ''' 向下滚动方法封装 :param driver: selenium对象 :param stop_length: 滚动终止值 :param step_length: 每次滚动步长 :return: ''' while True: # 终止不滚的条件 if stop_length - step_length <= 0: driver.execute_script(f'window.scrollBy(0, {stop_length})') break # 执行js代码 向下滚动 driver.execute_script(f'window.scrollBy(0, {step_length})') stop_length -= step_length time.sleep(1) # 1秒滚一下 # driver.execute_script('window.scrollBy(0, 30000)') if __name__ == '__main__': driver = Chrome() driver.get('https://news.163.com/') stop_length = 30000 # 终止值 step_length = 2000 # 每次滚动的值 # 循环5次点击加载更多 for i in range(1, 6): window_scroll(driver, stop_length, step_length) # 点击加载更多 more = driver.find_element(By.XPATH, '//*[@id="index2016_wrap"]/div[3]/div[2]/div[3]/div[2]/div[5]/div/a[3]/div[1]/span') # more.click() # 点击 driver.execute_script('arguments[0].click()', more) print(f'第:{i}次 点击加载更多') time.sleep(5) # 获取页面所有源代码 page = driver.page_source print(page)
直接调用型
el = driver.find_element_by_xxx(value)
# xxx是定位方式,后面我们会讲,value为该方式对应的值
使用By类型(需要导入By) 建议使用这种方式
# 直接掉用的方式会在底层翻译成这种方式
from selenium.webdriver.common.by import By
driver.find_element(By.xxx,value)
精确定位一个元素,返回结果为一个element对象,定位不到则报错
driver.find_element(By.xx, value) # 建议使用
driver.find_element_by_xxx(value)
定位一组元素,返回结果为element对象列表,定位不到返回空列表
driver.find_elements(By.xx, value) # 建议使用
driver.find_elements_by_xxx(value)
以下方法在element之后添加s就变成能够获取一组元素的方法
By.ID 使用id值定位
el = driver.find_element(By.ID, '')
el = driver.find_element_by_id()
By.XPATH 使用xpath定位
el = driver.find_element(By.XPATH, '')
el = driver.find_element_by_xpath()
By.TAG_NAME. 使用标签名定位
el = driver.find_element(By.TAG_NAME, '')
el = driver.find_element_by_tag_name()
By.LINK_TEXT使用超链接文本定位
el = driver.find_element(By.LINK_TEXT, '')
el = driver.find_element_by_link_text()
By.PARTIAL_LINK_TEXT 使用部分超链接文本定位
el = driver.find_element(By.PARTIAL_LINK_TEXT , '')
el = driver.find_element_by_partial_link_text()
By.NAME 使用name属性值定位
el = driver.find_element(By.NAME, '')
el = driver.find_element_by_name()
By.CLASS_NAME 使用class属性值定位
el = driver.find_element(By.CLASS_NAME, '')
el = driver.find_element_by_class_name()
By.CSS_SELECTOR 使用css选择器定位
el = driver.find_element(By.CSS_SELECTOR, '')
el = driver.find_element_by_css_selector()
注意:
find_element与find_elements区别
1. 只查找一个元素的时候:可以使用find_element(),find_elements()
find_element()会返回一个WebElement节点对象,但是没找到会报错,而find_elements()不会,之后返回一个空列表
2. 查找多个元素的时候:只能用find_elements(),返回一个列表,列表里的元素全是WebElement节点对象
3. 找到都是节点(标签)
4. 如果想要获取相关内容(只对find_element()有效,列表对象没有这个属性) 使用 .text
5. 如果想要获取相关属性的值(如href对应的链接等,只对find_element()有效,列表对象没有这个属性):使用 .get_attribute("href")
find_element_by_xxx方法仅仅能够获取元素对象,接下来就可以对元素执行以下操作 从定位到的元素中提取数据的方法
el.get_attribute(key) # 获取key属性名对应的属性值
el.text # 获取开闭标签之间的文本内容
el.click() # 对元素执行点击操作
el.submit() # 对元素执行提交操作
el.clear() # 清空可输入元素中的数据
el.send_keys(data) # 向可输入元素输入数据
## 1. 根据xpath定位元素: driver.find_elements(By.XPATH,"//*[@id='s']/h1/a") ## 2. 根据class定位元素: driver.find_elements(By.CLASS_NAME, "box") ## 3. 根据link_text定位元素: driver.find_elements(By.LINK_TEXT, "下载豆瓣 App") ## 4. 根据tag_name定位元素: driver.find_elements(By.TAG_NAME, "h1") ## 5. 获取元素文本内容: element.text ## 6. 获取元素标签属性: element.get_attribute("href") ## 7. 向输入框输入数据: element.send_keys(data)
我们已经基本了解了selenium的基本使用了. 但是呢, 不知各位有没有发现, 每次打开浏览器的时间都比较长. 这就比较耗时了. 我们写的是爬虫程序. 目的是数据. 并不是想看网页. 那能不能让浏览器在后台跑呢? 答案是可以的
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
opt = Options()
opt.add_argument("--headless")
opt.add_argument('--disable-gpu')
opt.add_argument("--window-size=4000,1600") # 设置窗口大小
web = Chrome(options=opt)
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
opt = Options()
opt.add_argument("--headless")
opt.add_argument('--disable-gpu')
web = Chrome(options=opt)
web.get('https://www.baidu.com')
print(web.title)
通过driver.get_cookies()
能够获取所有的cookie
获取cookie
dictCookies = driver.get_cookies()
设置cookie
driver.add_cookie(dictCookies)
删除cookie
#删除一条cookie
driver.delete_cookie("CookieName")
# 删除所有的cookie
driver.delete_all_cookies()
alert = driver.switch_to_alert()
driver.forward() # 前进
driver.back() # 后退
driver.refresh() # 刷新
driver.close() # 关闭当前窗口
driver.maximize_window() #最大化浏览器窗口
其流程可以描述如下:
注意:
scrapy的概念:Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架
scrapy框架的运行流程以及数据传递过程:
scrapy框架的作用:通过少量代码实现快速抓取
掌握scrapy中每个模块的作用:
引擎(engine):负责数据和信号在不同模块间的传递
调度器(scheduler):实现一个队列,存放引擎发过来的request请求对象
下载器(downloader):发送引擎发过来的request请求,获取响应,并将响应交给引擎
爬虫(spider):处理引擎发过来的response,提取数据,提取url,并交给引擎
管道(pipeline):处理引擎传递过来的数据,比如存储
下载中间件(downloader middleware):可以自定义的下载扩展,比如设置代理ip
爬虫中间件(spider middleware):可以自定义request请求和进行response过滤
理解异步和非阻塞的区别:异步是过程,非阻塞是状态
pip install scrapy==2.5.1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple scrapy==2.5.1
pip install scrapy-redis==0.7.2
如果安装失败. 请先升级一下pip. 然后重新安装scrapy即可.
最新版本的pip升级完成后. 安装依然失败, 可以根据报错信息进行一点点的调整, 多试几次pip. 直至success.
如果上述过程还是无法正常安装scrapy, 可以考虑用下面的方案来安装:
pip install wheel
https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
pip install Twisted‑21.7.0‑py3‑none‑any.whl
pip install pywin32
pip install scrapy
总之, 最终你的控制台输入scrapy version
能显示版本号. 就算成功了
## 创建scrapy项目的命令:
scrapy startproject +<项目名字>
## 示例:
scrapy startproject myspider
生成的目录和文件结果如下:
命令:在项目路径下执行:scrapy genspider +<爬虫名字> + <允许爬取的域名>
示例:
生成的目录和文件结果如下:
完善spider即通过方法进行数据的提取等操作
在/duanzi01/duanzi01/spiders/duanzi.py中修改内容如下:
import scrapy # 自定义spider类,继承scrapy.spider class DuanziSpider(scrapy.Spider): # 爬虫名字 name = 'duanzi' # 允许爬取的范围,防止爬虫爬到别的网站 allowed_domains = ['duanzixing.com'] # 开始爬取的url地址 start_urls = ['http://duanzixing.com/'] # 数据提取的方法,接受下载中间件传过来的response 是重写父类中的parse方法 def parse(self, response, **kwargs): # 打印抓取到的页面源码 # print(response.text) # xpath匹配每条段子的article列表 article_list = response.xpath('//article[@class="excerpt"]') # print(article_list) # 循环获取每一个article for article in article_list: # 匹配标题 # title = article.xpath('./header/h2/a/text()') # [<Selector xpath='./header/h2/a/text()' data='一个不小心就把2000块钱的包包设置成了50包邮'>] # title = article.xpath('./header/h2/a/text()')[0].extract() # 等同于 title = article.xpath('./header/h2/a/text()').extract_first() # 获取段子内容 con = article.xpath('./p[@class="note"]/text()').extract_first() print('title', title) print('con', con)
启动爬虫命令:
scrapy crawl duanzi
注意:
如果运行出现一下错误
AttributeError: module 'OpenSSL.SSL' has no attribute 'SSLv3_METHOD'
解决
1. 卸载cryptography:pip uninstall cryptography
2. 重新安装cryptography 36.0.2:pip install cryptography==36.0.2
3. 卸载pyOpenSSL:pip uninstall pyOpenSSL
4. 重新安装pyOpenSSL 22.0.0:pip install pyOpenSSL==22.0.0
发现会打印许多无用的info信息,我们需要关闭
(settings.py文件)
ROBOTSTXT_OBEY = False
robots是一种反爬协议。在协议中规定了哪些身份的爬虫无法爬取的资源有哪些。
在配置文件中setting,取消robots的监测:
在配置文件中配置全局的UA:USER_AGENT=‘xxxx’
在配置文件中加入日志等级:LOG_LEVEL = ‘ERROR’ 只输出错误信息
其它日志级别
CRITICAL 严重错误
ERROR 错误
WARNING 警告
INFO 消息
DEBUG 调试
代码实例:
# Scrapy settings for mySpider project
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36'
ROBOTSTXT_OBEY = False
## 没有这个配置项,自己加上
LOG_LEVEL = 'ERROR'
- response.url:# 当前响应的url地址
- response.request.url:## 当前响应对应的请求的url地址
- response.headers:## 响应头
- response.request.headers:## 当前响应的请求头
- response.body:## 响应体,也就是html代码,byte类型
- response.text ## 返回响应的内容 字符串
- response.status:## 响应状态码
- response.json() ## 抓取json数据
import scrapy class DzSpider(scrapy.Spider): name = 'dz' # 爬虫的名字 allowed_domains = ['duanzixing.com'] # 允许爬取的域名范围 start_urls = ['http://duanzixing.com/'] # 起始网址 def parse(self, response, **kwargs): print(response) print(response.url, '响应的URL') print(response.request.url, '响应对应请求的URL') print(response.request.headers, '响应对应请求的请求头') print(response.status, '响应状态码') print(response.body, '返回bytes 字节') print(response.text, '返回字符串') ## 如果返回的不是JSON数据,用这个属性会报错 print(response.json(), '返回JSON数据')
注意:
import scrapy class AiniSpider(scrapy.Spider): name = 'aini' # 当前爬虫的名字 allowed_domains = ['duanzixing.com'] # 允许爬取域名的范围 start_urls = ['http://duanzixing.com/'] # 起始爬取的URL地址,可以改的 ## 自己手动补一下 **kwargs def parse(self, response,**kwargs): ## 可以使用xpath语法 article_list = response.xpath('//article[@class="excerpt"]') for a in article_list: title1 = a.xpath('./header/h2/a/text()') # print(title) 打印发现还是selector对象, # [<Selector xpath='./header/h2/a/text()' data='你们发朋友圈我也发'>] # 需要用到response.xpath 对象的一个extract方法拿到字符串的内容,返回的是列表 title2 = a.xpath('./header/h2/a/text()').extract() # 但是里面就一个内容,所以可以用extract_first() title3 = a.xpath('./header/h2/a/text()').extract()[0] ## 跟下面的效果一样 title4 = a.xpath('./header/h2/a/text()').extract_first() print(title4) ## 你们发朋友圈我也发
response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
extract() 返回一个包含有字符串的列表
如果使用列表调用extract()则表示,extract会将列表中每一个列表元素进行extract操作,返回列表
extract_first() 返回列表中的第一个字符串,列表为空没有返回None
spider中的parse方法必须有
需要抓取的url地址必须属于allowed_domains,但是start_urls中的url地址没有这个限制
启动爬虫的时候注意启动的位置,是在项目路径下启动
scrapy shell是scrapy提供的一个终端工具,能够通过它查看scrapy中对象的属性和方法,以及测试xpath
使用方法:
scrapy shell http://www.baidu.com
在终端输入上述命令后,能够进入python的交互式终端,此时可以使用:
- response.xpath(): ## 直接测试xpath规则是否正确
- response.url:## 当前响应的url地址
- response.request.url:## 当前响应对应的请求的url地址
- response.headers:## 响应头
- response.body:## 响应体,也就是html代码,默认是byte类型
- response.request.headers:## 当前响应的请求头
在配置文件中存放一些公共变量,在后续的项目中方便修改,如:本地测试数据库和部署服务器的数据库不一致
- USER_AGENT ## 设置ua - ROBOTSTXT_OBEY ## 是否遵守robots协议,默认是遵守 - CONCURRENT_REQUESTS ## 设置并发请求的数量,默认是16个 - DOWNLOAD_DELAY ## 下载延迟,默认无延迟 (下载器在从同一网站下载连续页面之前应等待的时间(以秒为单位)。这可以用来限制爬行速度,以避免对服务器造成太大影响) - COOKIES_ENABLED ## 是否开启cookie,即每次请求带上前一次的cookie,默认是开启的 - DEFAULT_REQUEST_HEADERS ## 设置默认请求头,这里加入了USER_AGENT将不起作用 - SPIDER_MIDDLEWARES ## 爬虫中间件,设置过程和管道相同 - DOWNLOADER_MIDDLEWARES ## 下载中间件 - LOG_LEVEL ## 控制终端输出信息的log级别,终端默认显示的是debug级别的log信息 - LOG_LEVEL = "WARNING" - CRITICAL 严重 - ERROR 错误 - WARNING 警告 - INFO 消息 - DEBUG 调试 - LOG_FILE ## 设置log日志文件的保存路径,如果设置该参数,终端将不再显示信息 LOG_FILE = "./test.log" ## 其他设置参考:https://www.jianshu.com/p/df9c0d1e9087
代码配置
scrapy练习/duanzi/duanzi/spiders/dz.py
import scrapy class DzSpider(scrapy.Spider): name = 'dz' # allowed_domains = ['duanzixing.com'] start_urls = ['http://duanzixing.com/'] def parse(self, response,**kwargs): article_list = response.xpath('//article[@class="excerpt"]') for article in article_list: title = article.xpath('./header/h2/a/text()').extract_first() con = article.xpath('./p[@class="note"]//text()').extract_first() data = {'title':title,'con':con} ## 把数据传给管道 yield data
终端命令
只能存储csv格式,而且数据传给管道以后才可以用这个命令
## scrapy crawl 爬虫名称 -o 文件名.csv
scrapy crawl ITSpider -o ITSpider.csv
## 将文件存储到ITSpider.csv 文件中
思考:为什么要使用yield?
注意:yield能够传递的对象只能是:BaseItem,Request,dict,None
settings.py 打开当前注释
ITEM_PIPELINES = {
'doubanfile.pipelines.DoubanfilePipeline': 300,
}
pipeline中常用的方法:
1. process_item(self,item,spider): ## 实现对item数据的处理
2. open_spider(self, spider): ## 在爬虫开启的时候仅执行一次
3. close_spider(self, spider): ## 在爬虫关闭的时候仅执行一次
class DuanziPipeline:
## 开启的时候走一次
def open_spider(self,spider):
pass
# 每次yield的数据过来走一次
def process_item(self, item, spider):
return item
# scrapy之心完走一次
def close_spider(self,spider):
pass
代码配置
scrapy练习/duanzi/duanzi/spiders/dz.py
import scrapy class DzSpider(scrapy.Spider): name = 'dz' # allowed_domains = ['duanzixing.com'] start_urls = ['http://duanzixing.com/'] def parse(self, response,**kwargs): article_list = response.xpath('//article[@class="excerpt"]') for article in article_list: title = article.xpath('./header/h2/a/text()').extract_first() con = article.xpath('./p[@class="note"]//text()').extract_first() data = {'title':title,'con':con} ## 把数据传给管道 yield data
管道中连接数据库进行存储
from itemadapter import ItemAdapter import pymysql class DuanziPipeline: ## 开启的时候走一次 def open_spider(self,spider): # 连接MySQL数据库 self.db = pymysql.connect(host='127.0.0.1', user='root', password='aini5726', db='duanzi',port=3306) # 设置字符集 防止乱码 self.db.set_charset('utf8') # 创建游标对象 self.cursor = self.db.cursor() # 每次yield的数据过来走一次 def process_item(self, item, spider): ## item是传递过来的数据 ## 把数据存储到数据库中 title = item['title'] con = item['con'] try: sql = f'insert into dz set title = "{title}",con = "{con}"' self.cursor.execute(sql) self.db.commit() except Exception as e: print(sql,'===>',e) self.db.rollback() return item # scrapy之心完走一次 def close_spider(self,spider): ## 关闭数据库连接 self.db.close()
from itemadapter import ItemAdapter
class Duanzi01Pipeline:
def open_spider(self,spider):
self.f = open('duanzi.txt','w',encoding='utf-8')
def process_item(self, item, spider):
title = item['title']
con = item['con']
## 写入到文件中件中
self.f.write(f'{title}\n{con}\n')
return item
def close_spider(self,spider):
self.f.close()
首先在setting中开启管道
ITEM_PIPELINES = {
# 写入到MySQL数据库中
'duanzi03.pipelines.DuanziMYSQLPipeline': 300,
# 写入到文件中
'duanzi03.pipelines.DuanziFILEPipeline': 400,
}
然后写两个管道,分别把数据存储到数据库和文件中
from itemadapter import ItemAdapter import pymysql # 写入到MySQL数据库之 class DuanziMYSQLPipeline: # 开启一次 def open_spider(self, spider): # print(spider.name) # 连接MySQL数据库 self.db = pymysql.connect(host='127.0.0.1', user='root', password='123456', db='duanzi') # 设置字符编码 self.db.set_charset('utf8') # 创建游标对象 self.cursor = self.db.cursor() # 每次yield的数据过来走一次 def process_item(self, item, spider): title = item['title'] con = item['con'] try: sql = f'insert into dz(title, con) values("{title}", "{con}")' self.cursor.execute(sql) # 执行SQL语句 self.db.commit() # 事务提交 写入到MySQL数据库中 except Exception as e: print(sql, '===>', e) # 打印错误异常信息 self.db.rollback() # 事务回滚 # item是传递过来的数据 return item # scrapy执行完走一次 def close_spider(self, spider): # 关闭数据库连接 self.db.close() # 写入到文件中 class DuanziFILEPipeline: def open_spider(self, spider): self.f = open('duanzi.txt', 'w', encoding='UTF-8') def process_item(self, item, spider): title = item['title'] con = item['con'] # 写入到文件中 self.f.write(f'{title}\n{con}\n') return item # return不能去掉!!! def close_spider(self, spider): self.f.close()
思考:pipeline在settings中能够开启多个,为什么需要开启多个?
1. 不同的pipeline可以处理不同爬虫的数据,通过spider.name属性来区分
2. 不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存
3. 同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分
1. 使用之前需要在settings中开启
2. pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过
3. 有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None值
4. pipeline中process_item的方法必须有,否则item没有办法接受和处理
5. process_item方法接受item和spider,其中spider表示当前传递item过来的spider
6. open_spider(spider) :能够在爬虫开启的时候执行一次
7. close_spider(spider) :能够在爬虫关闭的时候执行一次
8. 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接
1. debug能够展示当前程序的运行状态
2. scrapy shell能够实现xpath的测试和对象属性和方法的尝试
3. scrapy的settings.py能够实现各种自定义的配置,比如下载延迟和请求头等
4. 管道能够实现数据的清洗和保存,能够定义多个管道实现不同的功能,其中有个三个方法
- process_item(self,item,spider):实现对item数据的处理
- open_spider(self, spider): 在爬虫开启的时候仅执行一次
- close_spider(self, spider): 在爬虫关闭的时候仅执行一次
pip install pillow
https://desk.zol.com.cn/dongman/
+ scrapy startproject desk
+ cd desk
+ scrapy genspider img desk.zol.com.cn/dongman
# 设置日志级别
LOG_LEVEL = 'ERROR'
ROBOTSTXT_OBEY = False
# 设置请求头
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
}
思路:
抓取到详情页中图片的url地址,交给图片管道进行下载
import scrapy from urllib.parse import urljoin class ImgSpider(scrapy.Spider): name = 'img' # allowed_domains = ['desk.zol.com.cn/dongman'] start_urls = ['http://desk.zol.com.cn/dongman/'] def parse(self, resp, **kwargs): # 先抓取到每个图片详情的url url_list = resp.xpath('//ul[@class="pic-list2 clearfix"]/li/a/@href').extract() # 获取到url列表后 进行循环进行每一个url详情页的请求 for url in url_list: # 因为抓取到的url并不完整,需要进行手动拼接 # urljoin('https://desk.zol.com.cn/dongman/', '/bizhi/8301_103027_2.html') url = urljoin('https://desk.zol.com.cn/dongman/', url) # 拼凑完发现当前url中有下载exe的url,将其去除 if url.find('exe') != -1: continue yield scrapy.Request(url, callback=self.parse_detail) # 对详情页进行解析 def parse_detail(self, resp): # 获取当前详情页中最大尺寸图片的url max_img_url = resp.xpath('//dd[@id="tagfbl"]/a/@href').extract() # 判断当前最大图片的url地址,为倒数第二个,如果当前图片列表url长度小于2 则当前证明不是图片的url if len(max_img_url) > 2: max_img_url = urljoin('https://desk.zol.com.cn/', max_img_url[0]) # 对url页面进行请求 获取最终大图的页面 yield scrapy.Request(max_img_url, callback=self.parse_img_detail) def parse_img_detail(self, resp): # 解析出大图的url img_src = resp.xpath("//img[1]/@src").extract_first() return {'img_src': img_src}
注意:
如果抓取过程中遇到如下报错,可能是cryptography 版本问题
twisted.web._newclient.ResponseNeverReceived: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', '', 'unsafe legacy renegotiation disabled')]>]
解决:
## 1、pip卸载cryptography:
pip uninstall cryptography
## 重新安装cryptography 36.0.2:
pip install cryptography==36.0.2
## 2、pip卸载pyOpenSSL:
pip uninstall pyOpenSSL
## 重新安装pyOpenSSL 22.0.0:
pip install pyOpenSSL==22.0.0
打开Pipelines文件夹
因为我们不能再像之前存储文本一样,使用之前的管道类(Pipeline),我们需要用到新的存储图片的管道类ImagesPipeline,因此我们需要先导入该类
pipelines.py
导入
from scrapy.pipelines.images import ImagesPipeline
定义一个Images类
from itemadapter import ItemAdapter from scrapy.pipelines.images import ImagesPipeline import scrapy class Imgspipline(ImagesPipeline): # 1. 发送请求(下载图片, 文件, 视频,xxx) def get_media_requests(self, item, info): # 获取到图片的url url = item['img_src'] # 进行请求 yield scrapy.Request(url=url, meta={"url": url}) # 直接返回一个请求对象即可 # 2. 图片存储路径 def file_path(self, request, response=None, info=None, *, item=None): # 当前获取请求的url的方式有2种 # 获取到当前的url 用于处理下载图片的名称 file_name = item['img_src'].split("/")[-1] # 用item拿到url # file_name = request.meta['url'].split("/")[-1] # 用meta传参获取 return file_name # 3. 可能需要对item进行更新 def item_completed(self, results, item, info): # print('results', results) for r in results: # 获取每个图片的路径 print(r[1]['path']) return item # 一定要return item 把数据传递给下一个管道
接着我们再定义一个保存数据的函数,并设置好存储的文件名,然后存储的路径需要在设置中(setting)文件中,添加IMAGE_STORE设置好存储的路径
开启图片管道
settings.py
ITEM_PIPELINES = {
'desk.pipelines.DeskPipeline': 300,
'desk.pipelines.Imgspipline': 400, # 开启图片管道
}
# 配置存储图片的路径
IMAGES_STORE = './imgs'
1. 直接携带cookies请求页面
2. 找url地址,发送post请求存储cookie
1. 直接携带cookies
2. 找url地址,发送post请求存储cookie
17k小说网
https://user.17k.com/
2.1 应用场景
1. cookie过期时间很长,常见于一些不规范的网站
2. 能在cookie过期之前把所有的数据拿到
3. 配合其他程序使用,比如其使用selenium把登陆之后的cookie获取到保存到本地,scrapy发送请求之前先读取本地cookie
2.2 通过修改settings中DEFAULT_REQUEST_HEADERS携带cookie
settings.py
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36',
'Cookie': 'ASP.NET_SessionId=n4lwamv5eohaqcorfi3dvzcv; xiaohua_visitfunny=103174; xiaohua_web_userid=120326; xiaohua_web_userkey=r2sYazfFMt/rxUn8LJDmUYetwR2qsFCHIaNt7+Zpsscpp1p6zicW4w=='
}
注意:需要打开COOKIES_ENABLED,否则上面设定的cookie将不起作用
# Disable cookies (enabled by default)
COOKIES_ENABLED = False
其他配置也别忘了
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'WARNING'
xiaoshuo.py
import scrapy
class DengluSpider(scrapy.Spider):
name = 'denglu'
allowed_domains = ['17k.com']
start_urls = ['https://user.17k.com/ck/user/mine/readList?page=1&appKey=2406394919']
def parse(self, res):
print(res.text)
局限性:
当前设定方式虽然可以实现携带cookie保持登录,但是无法获取到新cookie,也就是当前cookie一直是固定的,如果cookie是经常性变化,那么当前不适用
把cookie处理成字典的代码
cookie_str = 'GUID=bb4eef9b-1b8f-417e-9264-3cdc3bad8eb1; c_channel=0; c_csc=web; Hm_lvt_9793f42b498361373512340937deb2a0=1697788773,1698241318,1699877224; accessToken=avatarUrl%3Dhttps%253A%252F%252Fcdn.static.17k.com%252Fuser%252Favatar%252F18%252F98%252F90%252F96139098.jpg-88x88%253Fv%253D1650527904000%26id%3D96139098%26nickname%3D%25E4%25B9%25A6%25E5%258F%258BqYx51ZhI1%26e%3D1715429231%26s%3D14c085a75371ffc5; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2296139098%22%2C%22%24device_id%22%3A%2218b4c18c0cf39c-0a8b44b4b81a83-26031151-1866240-18b4c18c0d06e0%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%2C%22first_id%22%3A%22bb4eef9b-1b8f-417e-9264-3cdc3bad8eb1%22%7D' # 规律 """ key=val;key=val;key=val;key=val; """ # list_cookie = cookie_str.split(';') # print(list_cookie) """ cookie_dict = {} for cookie in cookie_str.split(';'): list_cookie = cookie.split('=') cookie_dict[list_cookie[0]] = list_cookie[1] # print(cookie_dict) """ cookie_dict = {cookie.split('=')[0]: cookie.split('=')[1] for cookie in cookie_str.split(';')} print(cookie_dict)
scrapy中start_url是通过start_requests来进行处理的,其实现代码如下,这样的话setting中的COOKIES_ENABLED = True不用关了。
def start_requests(self):
cls = self.__class__
if method_is_overridden(cls, Spider, 'make_requests_from_url'):
warnings.warn(
"Spider.make_requests_from_url method is deprecated; it "
"won't be called in future Scrapy releases. Please "
"override Spider.start_requests method instead (see %s.%s)." % (
cls.__module__, cls.__name__
),
)
for url in self.start_urls:
yield self.make_requests_from_url(url)
else:
for url in self.start_urls:
yield Request(url, dont_filter=True)
所以对应的,如果start_url地址中的url是需要登录后才能访问的url地址,则需要重写start_request方法并在其中手动添加上cookie
settings.py
import scrapy class DengluSpider(scrapy.Spider): name = 'denglu' # allowed_domains = ['https://user.17k.com/ck/user/mine/readList?page=1'] start_urls = ['https://user.17k.com/ck/user/mine/readList?page=1&appKey=2406394919'] def start_requests(self): cookies = 'GUID=796e4a09-ba11-4ecb-9cf6-aad19169267d; Hm_lvt_9793f42b498361373512340937deb2a0=1660545196; c_channel=0; c_csc=web; accessToken=avatarUrl%3Dhttps%253A%252F%252Fcdn.static.17k.com%252Fuser%252Favatar%252F18%252F98%252F90%252F96139098.jpg-88x88%253Fv%253D1650527904000%26id%3D96139098%26nickname%3D%25E4%25B9%25A6%25E5%258F%258BqYx51ZhI1%26e%3D1677033668%26s%3D8e116a403df502ab; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2296139098%22%2C%22%24device_id%22%3A%22181d13acb2c3bd-011f19b55b75a8-1c525635-1296000-181d13acb2d5fb%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%2C%22first_id%22%3A%22796e4a09-ba11-4ecb-9cf6-aad19169267d%22%7D; Hm_lpvt_9793f42b498361373512340937deb2a0=1661483362' cookie_dic = {} for i in cookies.split(';'): v = i.split('=') cookie_dic[v[0]] = v[1] # {i.split('=')[0]:i.split('=')[1] for i in cookies_str.split('; ')} # 简写 for url in self.start_urls: yield scrapy.Request(url, cookies=cookie_dic) def parse(self, response): print(response.text)
注意:
# 我们知道可以通过scrapy.Request()指定method、body参数来发送post请求;那么也可以使用scrapy.FormRequest()来发送post请求
通过scrapy.FormRequest能够发送post请求,同时需要添加fromdata参数作为请求体,以及callback
import scrapy class DlSpider(scrapy.Spider): name = 'dl' allowed_domains = ['17k.com'] start_urls = ['https://user.17k.com/ck/user/myInfo/96139098?bindInfo=1&appKey=2406394919'] def start_requests(self): # 登录的url地址 login_url = 'https://passport.17k.com/ck/user/login' data = { 'loginName': '17346570232', 'password': 'xlg17346570232' } # data = 'loginName=17346570232&password=xlg17346570232' # yield scrapy.Request(login_url, body=data, method='POST', callback=self.parse_do_login) yield scrapy.FormRequest(login_url, formdata=data, callback=self.parse_do_login) def parse_do_login(self, response, **kwargs): # 当前对于start_urls中的地址进行请求 yield scrapy.Request(self.start_urls[0]) def parse(self, response, **kwargs): print(response.text)
1. 找到post的url地址:点击登录按钮进行抓包,然后定位url地址为https://user.17k.com/ck/user/mine/readList?page=1&appKey=2406394919
2. 找到请求体的规律:分析post请求的请求体,其中包含的参数均在前一次的响应中
3. 否登录成功:通过请求个人主页,观察是否包含用户名
import scrapy class DengluSpider(scrapy.Spider): name = 'denglu' # allowed_domains = ['17k.com'] start_urls = ['https://user.17k.com/ck/user/mine/readList?page=1&appKey=2406394919'] def start_requests(self): ''' 请求登陆的账号和密码 ''' login_url = 'https://passport.17k.com/ck/user/login' # 使用request进行请求 # yield scrapy.Request(url=login_url, body='loginName=17346570232&password=xlg17346570232', callback=self.do_login, method='POST') # 使用Request子类FormRequest进行请求 自动为post请求 yield scrapy.FormRequest( url=login_url, formdata={'loginName': '17346570232', 'password': 'xlg17346570232'}, callback=self.do_login ) def do_login(self, response): ''' 登陆成功后调用parse进行处理 cookie中间件会帮我们自动处理携带cookie ''' for url in self.start_urls: yield scrapy.Request(url=url, callback=self.parse) def parse(self, response, **kwargs): print(response.text)
在settings.py中通过设置COOKIES_DEBUG=TRUE 能够在终端看到cookie的传递传递过程
注意关闭LOG_LEVEL
import base64 import json import requests url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', 'Referer':'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx', } data = { '__VIEWSTATE': 'mz408EoRxmRm5lSHqJ2RgFvDmJqOk/mNy0oQveKPJPdj0PQK8DemwQgoq6A8jEwXORmftrgcWnfuSWI1oRJrgp6x5kl+ouo08OG//6RLDTfZF2Zgi6K9midMNOik8R5upVE3d0PXol3YVxWLqkfOYpZRLHk=', '__VIEWSTATEGENERATOR': 'C93BE1AE', 'from': 'http://so.gushiwen.cn/user/collect.aspx', 'email': '793390457@qq.com', 'pwd': 'xlg17346570232', 'code': '', 'denglu': '登录' } def base64_api(uname, pwd, img, typeid): with open(img, 'rb') as f: base64_data = base64.b64encode(f.read()) b64 = base64_data.decode() data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64} result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text) if result['success']: return result["data"]["result"] else: #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别 return result["message"] return "" if __name__ == "__main__": img_path = "yzm.jpg" # 验证码地址 yzm_url = 'https://so.gushiwen.cn/RandCode.ashx' session = requests.Session() img_res = session.get(yzm_url) # 验证码存入本地 with open(img_path, 'wb') as f: f.write(img_res.content) # 识别验证码 result = base64_api(uname='luckyboyxlg', pwd='17346570232', img=img_path, typeid=3) print(result) # 验证码扔进表单 data['code'] = result # 请求登录接口 res = session.post(url, headers=headers, data=data) with open('gsw.html', 'w', encoding='UTF-8') as f: f.write(res.content.decode())
import scrapy import json import base64 import requests class DlSpider(scrapy.Spider): name = 'dl' # allowed_domains = ['dl.com'] start_urls = ['https://so.gushiwen.cn/RandCode.ashx'] def parse(self, response, **kwargs): img_path = "yzm.jpg" with open(img_path, 'wb') as f: f.write(response.body) # 识别验证码 result = self.base64_api(uname='luckyboyxlg', pwd='17346570232', img=img_path, typeid=3) print(result) data = { '__VIEWSTATE': 'mz408EoRxmRm5lSHqJ2RgFvDmJqOk/mNy0oQveKPJPdj0PQK8DemwQgoq6A8jEwXORmftrgcWnfuSWI1oRJrgp6x5kl+ouo08OG//6RLDTfZF2Zgi6K9midMNOik8R5upVE3d0PXol3YVxWLqkfOYpZRLHk=', '__VIEWSTATEGENERATOR': 'C93BE1AE', 'from': 'http://so.gushiwen.cn/user/collect.aspx', 'email': '793390457@qq.com', 'pwd': 'xlg17346570232', 'code': result, 'denglu': '登录' } # 请求登录接口 url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx' yield scrapy.FormRequest(url, formdata=data, callback=self.parse_login) def parse_login(self, response): with open('gsw.html', 'w', encoding='UTF-8') as f: f.write(response.text) def base64_api(self, uname, pwd, img, typeid): with open(img, 'rb') as f: base64_data = base64.b64encode(f.read()) b64 = base64_data.decode() data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64} result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text) if result['success']: return result["data"]["result"] else: #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别 return result["message"] return ""
from urllib.parse import urljoin import requests from lxml import etree headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', } url = 'https://www.shicimingju.com/bookmark/sidamingzhu.html' response = requests.get(url, headers=headers) con = response.content.decode() tree = etree.HTML(con) # print(con) book_list = tree.xpath('//div[@class="book-item"]/h3/a') for a in book_list: href = urljoin('https://www.shicimingju.com/',a.xpath('./@href')[0]) book_name = a.xpath('./text()')[0] print(href, book_name)
from urllib.parse import urljoin import requests from lxml import etree # 获取章节名称和url headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', } url = 'https://www.shicimingju.com/book/sanguoyanyi.html' response = requests.get(url, headers=headers) con = response.content.decode() tree = etree.HTML(con) # 获取所有的章节的超链接 mulu_list = tree.xpath('//div[@class="book-mulu"]/ul/li/a') for mulu in mulu_list: # 拼接完整的章节url mulu_url = urljoin('https://www.shicimingju.com/', mulu.xpath('./@href')[0]) # 获取章节名称 mulu_name = mulu.xpath('./text()')[0] print(mulu_url, mulu_name)
from urllib.parse import urljoin
import requests
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
}
url = 'https://www.shicimingju.com/book/sanguoyanyi/1.html'
response = requests.get(url, headers=headers)
con = response.content.decode()
tree = etree.HTML(con)
# 抓取章节的内容
con = ''.join(tree.xpath('//div[@class="card bookmark-list"]//text()'))
print(con)
import os.path import time from urllib.parse import urljoin import requests from lxml import etree import random def get_data(url): ''' 对url发起请求 返回tree对象 :param url: 请求的url地址 :return: tree对象 ''' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', } response = requests.get(url, headers=headers) con = response.content.decode() tree = etree.HTML(con) return tree def get_book(tree): ''' 获取书的名称和url地址 :param tree: 当前内容的tree对象 :return: 书名和url的字典 {'三国演义': ‘http://www......’} ''' book_list = tree.xpath('//div[@class="book-item"]/h3/a') book_href_name = {} for a in book_list: href = urljoin('https://www.shicimingju.com/', a.xpath('./@href')[0]) book_name = a.xpath('./text()')[0] # print(href, book_name) book_href_name[book_name] = href return book_href_name def get_mulu(tree): ''' 获取书章节的url和名称 :param tree: 请求章节内容返回的tree对象 :return: dict {'第一章': 'http://www....'} ''' mulu_list = tree.xpath('//div[@class="book-mulu"]/ul/li/a') mulu_href_name = {} for mulu in mulu_list: # 拼接完整的章节url mulu_url = urljoin('https://www.shicimingju.com/', mulu.xpath('./@href')[0]) # 获取章节名称 mulu_name = mulu.xpath('./text()')[0] # print(mulu_url, mulu_name) mulu_href_name[mulu_name] = mulu_url return mulu_href_name def get_content(book_name, mulu_name, tree): ''' 将章节内容写入到本地 :param book_name: 书的名称 :param mulu_name: 章节名称 :param tree: tree对象 :return: None ''' # 如果当前书的目录不存在 则创建 if not os.path.exists(book_name): os.mkdir(book_name) # 抓取章节的内容 con = ''.join(tree.xpath('//div[@class="card bookmark-list"]//text()')) # print(con) with open(os.path.join(book_name, mulu_name+'.txt'), 'w', encoding='UTF-8') as f: f.write(con) print(book_name, mulu_name, '下载完成') if __name__ == '__main__': url = 'https://www.shicimingju.com/bookmark/sidamingzhu.html' # 获取书的url和书的名称 for book_name, book_url in get_book(get_data(url)).items(): # 循环获取章节和章节url for mulu_name, mulu_url in get_mulu(get_data(book_url)).items(): # 进行下载 get_content(book_name, mulu_name, get_data(mulu_url)) time.sleep(random.randint(1,3))
scrapy.Request(url[,callback,method="GET",headers,body,cookies, meta,dont_filter=False])
参数解释
1. 中括号中的参数为可选参数
2. callback:## 表示当前的url的响应交给哪个函数去处理
3. meta:## 实现数据在不同的解析函数中传递,meta默认带有部分数据,比如下载延迟,请求深度等
4. dont_filter:## 默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动
5. method:## 指定POST或GET请求
6. headers:## 接收一个字典,其中不包括cookies
7. cookies:## 接收一个字典,专门放置cookies
8. body: ## 传递数据 字符串格式 为POST的数据
meta的形式:字典
meta的作用:meta可以实现数据在不同的解析函数中的传递
在爬虫文件的parse方法中,提取详情页增加之前callback指定的parse_detail函数:
def parse(self,response):
...
yield scrapy.Request(detail_url, callback=self.parse_detail,meta={"item":item})
...
def parse_detail(self,response):
#获取之前传入的item
item = resposne.meta["item"]
特别注意
proxy
,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件的学习中进行介绍爬虫的代码
import scrapy class SdmzSpider(scrapy.Spider): name = 'sdmz' # allowed_domains = ['www.com'] start_urls = ['https://www.shicimingju.com/bookmark/sidamingzhu.html'] def parse(self, response, **kwargs): # 获取四大名著的书名和url a_list = response.xpath('//div[@class="book-item"]/h3/a') for a in a_list: book_url = response.urljoin(a.xpath('./@href').extract_first()) book_name = a.xpath('./text()').extract_first() # print(book_url, book_name) # 传递书给下一个方法 用于最终创建书存储的路径 yield scrapy.Request(book_url, callback=self.parse_mulu, meta={'book_name': book_name}) # 处理书的章节的 def parse_mulu(self, response, **kwargs): book_name = response.meta['book_name'] # 获取书的章节和url a_list = response.xpath('//div[@class="book-mulu"]/ul/li/a') for a in a_list: mulu_url = response.urljoin(a.xpath('./@href').extract_first()) mulu_name = a.xpath('./text()').extract_first() # 对章节url进行请求 并携带当前书名 章节名称 方便后续创建存储目录 yield scrapy.Request(mulu_url, callback=self.parse_content, meta={'book_name': book_name, 'mulu_name': mulu_name}) # 解析章节的内容 def parse_content(self, response, **kwargs): # 获取书名, 章节名称,目的为了管道中进行目录层级的创建 book_name = response.meta['book_name'] mulu_name = response.meta['mulu_name'] contnet = ''.join(response.xpath('//div[@class="card bookmark-list"]//text()').extract()) # 将书名,章节名,章节的内容传递到管道 进行下载 yield {'book_name': book_name, 'mulu_name': mulu_name, 'contnet': contnet} # print({'book_name': book_name, 'mulu_name': mulu_name, 'contnet': contnet})
管道的代码
# Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html # useful for handling different item types with a single interface from itemadapter import ItemAdapter import os class SidamingzhuPipeline: def process_item(self, item, spider): # 书名 book_name = item['book_name'] # 章节名称 mulu_name = item['mulu_name'] # 章节内容 contnet = item['contnet'] # 判断当前书的目录是否存在 # if not os.path.exists(book_name): # os.mkdir(book_name) total = '四大名著' # 加一个总目录 """ 四大名著 水浒 三国 ... """ dir = os.path.join(total, book_name) if not os.path.exists(dir): os.makedirs(dir) # 递归创建 # 拼接路径 书的路径 path = os.path.join(dir, mulu_name+'.txt') # 写入到本地 with open(path, 'w', encoding='UTF-8') as f: f.write(contnet) print(book_name, mulu_name, '下载完成')
定义item即提前规划好哪些字段需要抓取,scrapy.Field()仅仅是提前占坑,通过item.py能够让别人清楚自己的爬虫是在抓取什么,同时定义好哪些字段是需要抓取的,没有定义的字段不能使用,防止手误
在python大多数框架中,大多数框架都会自定义自己的数据类型(在python自带的数据结构基础上进行封装),目的是增加功能,增加自定义异常
在items.py文件中定义要提取的字段:
import scrapy
class DoubanItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
name = scrapy.Field() # 电影名称
director = scrapy.Field() # 导演
screenwriter = scrapy.Field() # 编剧
to_star = scrapy.Field() # 主演
回顾之前的代码中,我们有很大一部分时间在寻找下一页的url地址或者是内容的url地址上面,这个过程能更简单一些么?
思路:
对应的crawlspider就可以实现上述需求,能够匹配满足条件的url地址,组装成Reuqest对象后自动发送给引擎,同时能够指定callback函数
即:crawlspider爬虫可以按照规则自动获取连接
scrapy startproject project
cd project
scrapy genspider -t crawl dz duanzixing.com
进行settings.py配置
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule ## 继承的是CrawISpider class DzSpider(CrawlSpider): name = 'dz' # allowed_domains = ['dz.com'] start_urls = ['https://duanzixing.com/'] # 规则 ''' https://duanzixing.com/page/2/ https://duanzixing.com/page/3/ ''' rules = ( # 参数一 链接提取器 必须的(给定一个规则 他舅会按照这个规则进行url的匹配并请求)默认使用的是正则!!! # 参数二 回调 请求后的响应交给谁 跟之前的区别是 没有self # 参数三 是否进入再次匹配 如果不想改为False # Rule(LinkExtractor(allow=r'page/\d+/'), callback='parse_item', follow=False), # Rule(LinkExtractor(allow=r'page/\d+/'), callback='parse_item', follow=True), # Rule(LinkExtractor(allow=r'https://duanzixing\.com/page/\d+/'), callback='parse_item', follow=False), # 抓取所有超链接 # Rule(LinkExtractor(allow=r''), callback='parse_item', follow=False), # 使用正则进行匹配 Rule(LinkExtractor(restrict_xpaths='/html/body/section/div/div/div[2]/ul/li/a'), callback='parse_item', follow=False), ) def parse_item(self, response): item = {} print(response.url) # print(response.url) # response.xpath('//article[@class="excerpt"]/header/a/text()').extract() # response.xpath('//article[@class="excerpt"]/p[@class="note"]/text()').extract() # article_list = response.xpath('//article[@class="excerpt"]') # for article in article_list: # title = article.xpath('./header/h2/a/text()').extract_first() # con = article.xpath('./p[@class="note"]/text()').extract_first() # print(title, con, response.url) return item
在crawlspider爬虫中,没有parse函数
重点在rules中:
scrapy genspider -t crawl <爬虫名> <allowed_domail>
创建一个crawlspider的模板,页可以手动创建allow: 满足括号中的’re’表达式的url会被提取,如果为空,则全部匹配
deny: 满足括号中的’re’表达式的url不会被提取,优先级高于allow
allow_domains: 会被提取的链接的domains(url范围),如:['https://movie.douban.com/top250']
deny_domains: 不会被提取的链接的domains(url范围)
restrict_xpaths: 使用xpath规则进行匹配,和allow共同过滤url,即xpath满足的范围内的url地址会被提取
如:restrict_xpaths='//div[@class="pagenav"]'
restrict_css: 接收一堆css选择器, 可以提取符合要求的css选择器的链接
attrs: 接收一堆属性名, 从某个属性中提取链接, 默认href
tags: 接收一堆标签名, 从某个标签中提取链接, 默认a, area
值得注意的, 在提取到的url中, 是有重复的内容的. 但是我们不用管. scrapy会自动帮我们过滤掉重复的url请求
模拟使用
正则用法: links1 = LinkExtractor(allow=r’list_23_\d+.html’)
xpath用法: links2 = LinkExtractor(restrict_xpaths=r’//div[@class=“x”]')
css用法: links3 = LinkExtractor(restrict_css=‘.x’)
5.提取连接
Rule常见参数
1. 主要功能是在爬虫运行过程中进行一些处理,如对非200响应的重试(重新构造Request对象yield给引擎)
2. 也可以对header以及cookie进行更换和处理
3. 其他根据业务需求实现响应的功能
但在scrapy默认的情况下 两种中间件都在middlewares.py一个文件中
爬虫中间件使用方法和下载中间件相同,常用下载中间件
接下来我们对爬虫进行修改完善,通过下载中间件来学习如何使用中间件 编写一个Downloader Middlewares和我们编写一个pipeline一样,定义一个类,然后在setting中开启
Downloader Middlewares默认的方法:在中间件类中,有时需要重写处理请求或者响应的方法】
process_request(self, request, spider):【此方法是用的最多的】
- ## 当每个request通过下载中间件时,该方法被调用。
- ## 返回None值:继续请求 没有return也是返回None,该request对象传递给下载器,或通过引擎传递给其他权重低的process_request方法 【如果所有的下载器中间件都返回为None,则请求最终被交给下载器处理】
- ## 返回Response对象:不再请求,把response返回给引擎【如果返回为请求,则将请求交给调度器】
- ## 返回Request对象:把request对象交给调度器进行后续的请求
process_response(self, request, response, spider):
- ## 当下载器完成http请求,传递响应给引擎的时候调用
- ## 返回Resposne:通过引擎交给爬虫处理或交给权重更低的其他下载中间件的process_response方法 【如果返回为请求,则将请求交给调度器】
- ## 返回Request对象:交给调取器继续请求,此时将不通过其他权重低的process_request方法
process_exception(self, request, exception, spider):
请求出现异常的时候进行调用
比如当前请求被识别为爬虫 可以使用代理
def process_exception(self, request, exception, spider):
request.meta['proxy'] = 'http://ip地址'
request.dont_filter = True # 因为默认请求是去除重复的,因为当前已经请求过,所以需要设置当前为不去重
return request # 将修正后的对象重新进行请求
在settings.py中配置开启中间件,权重值越小越优先执行 【同管道的使用方式相同】
spider参数:为爬虫中类的实例化可以在这里进行调用爬虫中的属性
如:spider.name
spiders.py(爬虫代码)news.163.com/domestic/
import scrapy
class DzSpider(scrapy.Spider):
name = 'dz'
# allowed_domains = ['duanzixing.com']
start_urls = ['https://duanzixing.com/']
def parse(self, response, **kwargs):
print(response.request.headers)
settings.py(开启中间件)
也别忘了其他的配置(前面学习过程中讲过了)
DOWNLOADER_MIDDLEWARES = {
'wangyi.middlewares.WangyiDownloaderMiddleware': 543,
}
middlewares.py
from scrapy import signals # 导入随机ua from zhongjianjian01.settings import USER_AGENTS_LIST import random class Zhongjianjian01DownloaderMiddleware: # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the downloader middleware does not modify the # passed objects. @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_request(self, request, spider): # 随机ua ua = random.choice(USER_AGENTS_LIST) request.headers['User-Agent'] = ua print(request.headers['User-Agent']) print('process_request') return None def process_response(self, request, response, spider): print('process_response') return response def process_exception(self, request, exception, spider): print('process_exception', exception) def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
运行查看
USER_AGENTS_LIST = [
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
]
import random
from Tencent.settings import USER_AGENTS_LIST # 注意导入路径,请忽视pycharm的错误提示
class UserAgentMiddleware(object):
def process_request(self, request, spider):
user_agent = random.choice(USER_AGENTS_LIST)
request.headers['User-Agent'] = user_agent
class CheckUA:
def process_response(self,request,response,spider):
print(request.headers['User-Agent'])
DOWNLOADER_MIDDLEWARES = {
'Tencent.middlewares.UserAgentMiddleware': 543,
}
代理添加的位置:request.meta中增加proxy
字段
获取一个代理ip,赋值给
request.meta['proxy']
class ProxyMiddleware(object):
def process_request(self,request,spider):
proxy = random.choice(proxies) # proxies可以在settings.py中,也可以来源于代理ip的webapi
# proxy = 'http://192.168.1.1:8118'
request.meta['proxy'] = proxy
return None # 可以不写return
在使用了代理ip的情况下可以在下载中间件的process_response()方法中处理代理ip的使用情况,如果该代理ip不能使用可以替换其他代理ip
class ProxyMiddleware(object):
def process_response(self, request, response, spider):
if response.status != '200' and response.status != '302':
#此时对代理ip进行操作,比如删除
return request
网址:https://www.kuaidaili.com/
我们是scrapy使用隧道 所以选择 Python-Scrapy
按照步骤开启中间件与填写自己的用户名与密码等信息即可
中间件的使用:
url:https://news.163.com/
抓取 国内 国际 军事 航空
分析
国内等数据是由动态加载的 并不是跟着当前的请求一起返回的
解决方式2种
通过selenium配合爬虫抓取页面进行数据
找到加载动态数据的url地址 通过爬虫进行抓取
将找到的URL放到浏览器中进行请求 效果如下
配置文件处理settings.py
# Scrapy settings for wangyi project BOT_NAME = 'wangyi' SPIDER_MODULES = ['wangyi.spiders'] NEWSPIDER_MODULE = 'wangyi.spiders' # 默认请求头 USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36' # 用于更换随机请求头 USER_AGENTS_LIST = [ "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5" ] LOG_LEVEL = 'ERROR' ROBOTSTXT_OBEY = False DOWNLOADER_MIDDLEWARES = { 'wangyi.middlewares.WangyiDownloaderMiddleware': 543, } ITEM_PIPELINES = { 'wangyi.pipelines.WangyiPipeline': 300, }
爬虫代码wy.py
import scrapy from selenium.webdriver import Chrome from selenium.webdriver.chrome.options import Options # 一定重新下载谷歌驱动!!!!! class WySpider(scrapy.Spider): name = 'wy' opt = Options() opt.add_argument("--headless") # opt.add_argument('--disable-gpu') # 实例化selenium对象 web = Chrome(options=opt) # allowed_domains = ['news.163.com/domestic/'] start_urls = ['https://news.163.com/'] li_index = [1, 2] url_list = [] # 存放国内和国际的url def parse(self, response, **kwargs): # 抓取我们要访问的url 国内 国际 menu_list = response.xpath('//div[@class="ns_area list"]/ul/li/a/@href').extract() for i in range(len(menu_list)): if i in self.li_index: url = menu_list[i] # 取出国内 国际的url self.url_list.append(url) yield scrapy.Request(url, callback=self.parse_page) # 解析国内,国际页面的数据 def parse_page(self, response, **kwargs): # 解析新闻详情的url page_detail_url = response.xpath('//div[@class="ndi_main"]/div/a/@href').extract() for url in page_detail_url: yield scrapy.Request(url, callback=self.parse_page_detail) # 解析新闻详情 def parse_page_detail(self, response, **kwargs): title = response.xpath('//h1[@class="post_title"]/text()').extract_first() # 新闻内容 con = ''.join(response.xpath('//div[@class="post_main"]//text()').extract()) data = {'title': title, 'con': con} print(data) yield data
Middlewares.py
import time from scrapy import signals from itemadapter import is_item, ItemAdapter from scrapy.http import HtmlResponse rom wangyi.settings import USER_AGENTS_LIST import random class WangyiDownloaderMiddleware: # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the downloader middleware does not modify the # passed objects. @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_request(self, request, spider): ua = random.choice(USER_AGENTS_LIST) request.headers['User-Agent'] = ua return None def process_response(self, request, response, spider): driver = spider.web # 拿到selenium对象 url = request.url if url in spider.url_list: driver.get(url) # 开始访问的url # 滚动条滚动到底部 driver.execute_script('window.scrollTo(0, document.body.scrollHeight)') time.sleep(random.randint(1, 3)) driver.execute_script('window.scrollTo(0, document.body.scrollHeight)') time.sleep(random.randint(1, 3)) source = driver.page_source # 获取页面源代码 # 篡改响应 return HtmlResponse(url=url, body=source, encoding='UTF-8', request=request) return response def process_exception(self, request, exception, spider): # Called when a download handler or a process_request() # (from other downloader middleware) raises an exception. # Must either: # - return None: continue processing this exception # - return a Response object: stops process_exception() chain # - return a Request object: stops process_exception() chain pass def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
学习目标
在前面scrapy框架中我们已经能够使用框架实现爬虫爬取网站数据,如果当前网站的数据比较庞大, 我们就需要使用分布式来更快的爬取数据
安装
pip install scrapy_redis == 0.7.2
Scrapy_redis在scrapy的基础上实现了更多,更强大的功能,具体体现在:
那么,在这个基础上,如果需要实现分布式,即多台服务器同时完成一个爬虫,需要怎么做呢?
具体流程如下:
由于时间关系,大家对redis的命令遗忘的差不多了, 但是在scrapy_redis中需要使用redis的操作命令,所有需要回顾下redis的命令操作
redis是一个开源的内存型数据库,支持多种数据类型和结构,比如列表、集合、有序集合等,同时可以使用redis-manger-desktop等客户端软件查看redis中的数据,关于redis-manger-desktop的使用可以参考扩展阅读
redis-server.exe redis.windows.conf
启动服务端redis-cli
客户端启动select 1
切换dbkeys *
查看所有的键type 键
查看键的类型flushdb
清空dbflushall
清空数据库redis的命令很多,这里我们简单复习后续会使用的命令
scarpy_redis的分布式工作原理
配置完成使用分布式爬虫
分布式爬虫
(必须). 使用了scrapy_redis的去重组件,在redis数据库里做去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
(必须). 使用了scrapy_redis的调度器,在redis里分配请求
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
(可选). 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True
(必须). 通过配置RedisPipeline将item写入key为 spider.name : items 的redis的list中,供后面的分布式处理item 这个已经由 scrapy-redis 实现,不需要我们写代码,直接使用即可
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 100 ,
}
(必须). 指定redis数据库的连接参数
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
REDIS_DB = 0 # (不指定默认为0)
# 设置密码
REDIS_PARAMS = {'password': '123456'}
settings.py
这几行表示scrapy_redis
中重新实现的了去重的类,以及调度器,并且使用的RedisPipeline
需要添加redis的地址,程序才能够使用redis
在settings.py文件修改pipelines,增加scrapy_redis。
# 配置分布式
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400,
}
# 或者使用下面的方式
REDIS_HOST = "127.0.0.1"
REDIS_PORT = 6379
REDIS_PARAMS = {'password': '123456'}
注意:scrapy_redis的优先级要调高
from scrapy_redis.spiders import RedisCrawlSpider
# 注意 一定要继承RedisCrawlSpider
class FbsSpider(RedisCrawlSpider):
name = 'fbs'
# allowed_domains = ['www.xxx.com']
# start_urls = ['http://www.xxx.com/']
redis_key = 'fbsQueue' # 使用管道名称(课根据实际功能起名称)
scrapy-redis中都是用key-value形式存储数据,其中有几个常见的key-value形式:
1、 “项目名:items” -->list 类型,保存爬虫获取到的数据item 内容是 json 字符串
2、 “项目名:dupefilter” -->set类型,用于爬虫访问的URL去重 内容是 40个字符的 url 的hash字符串
3、 “项目名:requests” -->zset类型,用于scheduler调度处理 requests 内容是 request 对象的序列化 字符串
网址
阳光问政为例
https://wz.sun0769.com/political/index/politicsNewest
settings.py
BOT_NAME = 'fbsPro' SPIDER_MODULES = ['fbsPro.spiders'] NEWSPIDER_MODULE = 'fbsPro.spiders' USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36' # Obey robots.txt rules ROBOTSTXT_OBEY = False # 配置分布式 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" SCHEDULER = "scrapy_redis.scheduler.Scheduler" SCHEDULER_PERSIST = True ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400, } # 或者使用下面的方式 REDIS_HOST = "127.0.0.1" REDIS_PORT = 6379 REDIS_PARAMS = {'password': '123456'}
fbs.py
实现方式就是之前的crawlspider
类型的爬虫
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from scrapy_redis.spiders import RedisCrawlSpider # 注意 一定要继承RedisCrawlSpider class FbsSpider(RedisCrawlSpider): name = 'fbs' # allowed_domains = ['www.xxx.com'] # start_urls = ['http://www.xxx.com/'] redis_key = 'fbsQueue' # 使用管道名称 link = LinkExtractor(allow=r'/political/politics/index?id=\d+') rules = ( Rule(link, callback='parse_item', follow=True), ) def parse_item(self, response): item = {} yield item
redis中
redis-windwos.conf
启动redis
队列中添加url地址
添加:lpush fbsQueue https://wz.sun0769.com/political/index/politicsNewest
查看:lrange fbsQueue 0 -1
运行
scrapy crawl fbs
去redis中查看存储的数据
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。