scrapy环境搭建使用小结
最近研究了一下网页爬虫相关技术,在此进行一下总结,详情如下。
一、静态爬取
最开始研究爬取数据,是纯手工python实现,使用requests获取网页内容,再使用BeautifulSoup对获取的html网页内容进行解析,并提取所需要的字段内容保存下来,从而完成所需内容信息的爬取。相关依赖安装如下:
pip install requests
pip install beautifulsoup4
如果在工作机上,原本有不同的项目, 需要用到同一个包的不同版本,使用上面的命令直接进行安装, 新版本会覆盖以前的版本,原有的项目就无法运行了。要规避该问题,可以为数据爬取新建一个虚拟环境,后续在该虚拟环境进行相关操作,创建虚拟环境可参考下面的链接:
https://docs.python.org/zh-cn/3/tutorial/venv.html
不过使用这种方式爬取数据,有一个前提条件,那就是目标数据就在网页源代码中,
请求获取到网页源代码,就能从中提取出想要的数据。代码片断如下:
import requests
from bs4 import BeautifulSoup
import re
class MyCrawler:
def get_html_content(self, url_to_get):
# 构建请求参数
params = None
# 发送请求
response = requests.get(url_to_get, params=params)
data = response.text.encode("latin1").decode("gbk")
# print(data)
return data
def get_chapter_content(self, data):
# 将html传入BeautifulSoup 的构造方法,得到一个文档的对象
soup = BeautifulSoup(data, 'html.parser', from_encoding='utf-8')
# 查找所有的h1标签
title = soup.h1.string
# print(title)
content = soup.find('div', id='content').text
# print(content)
whole = '\r\n'.join([title, content])
# print(whole)
ptn_str = '([…!?。”])([!?。”]?[\u4E00 -\u9FA5 ]*"[a-zA-Z_]+\d+"[^\r\n]+)'
return re.sub(ptn_str, '\g<1>', whole)
如果目标数据不在网页源代码中,而是在浏览器加载网页源码之后,通过执行JavaScript 脚本语言动态从服务器获取数据,则静态爬取的方式就不适用了,这种情况下可以使用动态爬取的方式获取数据。
二、动态爬取
动态爬取,简单地说,就是使用一个真实的浏览器(或无界面浏览器),让页面源码正常加载运行,完成动态内容的加载,然后,再通过查询页面DOM 来获取所要寻找的内容。部分情况下,要获取到目标数据,还需要让浏览器自动模拟人进行一系列的操作,才能让浏览器加载目标内容,从而进行爬取。相关依赖安装如下:
pip install selenium
当前动态爬取使用比较普遍的浏览器是chrome,官方下载地址如下:
https://www.google.cn/chrome/
另外配套使用的chromedriver,下载地址如下:
http://npm.taobao.org/mirrors/chromedriver/
需要注意的是,下载chromedriver的版本,要和前面下载的chrome浏览器版本相匹配,否则实际使用的时候会报错,无法正常使用。
实际使用时,要确保chromedriver已经配置在了系统环境变量中,否则在创建使用时需要指定目录,代码片断如下:
from selenium import webdriver
import re
class MyCrawler:
driver = None
def get_content_driver(self, url_to_get):
cls_obj = self.__class__
if not cls_obj.driver:
cls_obj.driver = webdriver.Chrome()
cls_obj.driver.maximize_window()
cls_obj.driver.get(url_to_get)
time.sleep(5)
return cls_obj.driver
def get_chapter_content(self, driver):
title = driver.find_element_by_tag_name('h1').text.strip()
print('chapter title: {}'.format(title))
content_div = driver.find_element_by_id('chapter_content')
src_content = content_div.text
end_str = '如果您觉得《xxxx》还不错的话'
end_index = src_content.find(end_str)
mid_content = src_content[:end_index]
# print(mid_content)
content = re.sub('\n\n', '\n', mid_content)
whole = '\r\n'.join([title, content])
# print(whole)
return whole
三、linux动态爬取准备工作
实际操作过程中,想在linux机器上进行动态数据的爬取,我的工作机器是windows,需要远程连接到linux机器上进行相关操作,本机有装xshell,但研究再三,未能找到使用xshell触发linux进行动态爬取的有效方式,最后采用VNC远程登录的方式解决了该问题,下面详情描述整个过程。
1. chrome浏览器安装
我使用的linux机器安装的是Centos7.7.1908版本,首先安装chrome浏览器,具体操作可参考下面的链接:
Centos7 yum安装chrome浏览器
2.vnc服务器端及客户端的安装使用
服务器端安装vnc软件可参考下面的链接:
CentOS7.x安装VNC实录
VNC客户端可从下面的链接地址下载,再进行安装:
https://www.realvnc.com/en/connect/download/viewer/windows/
实际操作中,发现存在一台linux服务器,只支持一个vnc连接的限制,所以如果要以某个帐户进行VNC登录,则需要先使用xshell类似的工具远程以该帐户登录,然后运行相应命令启动服务才能使用VNC客户端进行远程连接,相关命令如下:
vnc启动:vncserver :1
vnc停止:vncserver -kill :1
四、使用scrapy进行数据爬取
前面简单描述了直接使用python脚本语言进行静态数据爬取和动态数据爬取的操作方式,如果只是进行少量数据爬取,则问题不大,如果进行大批量的数据爬取,则建议使用专业的爬虫框架scrapy,下面分别进行说明。
1.scrapy的安装
安装python3运行环境,具体可参考下面这个链接,python版本可去官网下载相对较新版本:
Centos7.6安装Python3(与yum共存)
scrapy的安装可直接参考官方文档(如果原有python项目依赖于包的较老版本,为了避免相应影响,可新建一个虚拟环境安装scrapy,详情可参见本文前面的描述):
https://docs.scrapy.org/en/latest/intro/install.html
2.scrapy使用小结
scrapy使用简介可参考下面的链接:
https://docs.scrapy.org/en/latest/intro/tutorial.html
下面我将根据我的实际体验,对比较重要的点,依次进行说明。
(1) scrapy并行爬取
下面直接引用官方的并行触发样例,同时触发多条请求,异步进行数据请求解析操作。
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
page = response.url.split("/")[-2]
filename = f'quotes-{page}.html'
with open(filename, 'wb') as f:
f.write(response.body)
self.log(f'Saved file {filename}')
(2) scrapy链式爬取
下面直接引用官方的链式触发样例,在每次数据解析完成之后,同时触发下一次数据的爬取操作,使得整体数据的爬取串行进行,整体流程简单,在此基础上可灵活配置。
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
]
def parse(self, response):
for quote in response.css('div.quote'):
yield {
'text': quote.css('span.text::text').get(),
'author': quote.css('small.author::text').get(),
'tags': quote.css('div.tags a.tag::text').getall(),
}
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback=self.parse)
(3) scrapy ROBOTSTXT_OBEY = True的相关说明
调试自定义的DownloaderMiddleware代码时,发现每次启动scrapy,都会先触发一个网络请求,该请求的request.meta中包含一个键为“dont_obey_robotstxt”的数据,实际处理时会造成一定的干扰,百度搜索了一下,需要在setting.py文件中将“ROBOTSTXT_OBEY = True”调整为“ROBOTSTXT_OBEY = False”,即可解决该问题,参考链接如下:
scrapy中ROBOTSTXT_OBEY = True的相关说明
(4) 解决保存的json序列数据编码不是utf-8编码的问题
查看获取到的数据,发现一个现象,scrapy控制台信息显示爬取到的数据是中文,但查看最终存储的文件内容却不是中文,最后发现可通过在setting.py增加一行配置解决该问题,具体添加信息如下:
FEED_EXPORT_ENCODING = 'utf-8'
参考链接如下:
scrapy中修改爬取数据的输出编码为utf-8
(5) scrapy url过滤问题
调试过程中发现了一个很奇怪的现象,启动scrapy之后,触发一次请求之后,后面的请求直接失败,查看日志发现报“DEBUG: Filtered duplicate request----no more duplicates will be shown”错误,解决方案为在Request中添加dont_filter=True(强制不过滤)的参数,示例代码如下:
request = scrapy.Request(url=novelLink, meta={'novelId': novelId}, dont_filter=True, callback=self.parse_book_detail)
yield request
参考链接如下:
Scrapy分布式爬虫过滤问题
3.结合chromedriver进行动态数据爬取
(1) 配置chromedriver
直接上代码:
from scrapy.http import HtmlResponse
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
class TutorialDownloaderMiddleware:
def __init__(self):
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 设置无界面
self.driver = webdriver.Chrome(chrome_options=options) # 初始化Chrome驱动
def __del__(self):
# 使用quit方法,才能完全退出,另外chrome浏览器默认勾选了 “关闭 Google Chrome 后继续运行后台应用”选项,需要去掉。
self.driver.quit()
def process_request(self, request, spider):
try:
self.driver.get(request.url) # 获取网页链接内容
return HtmlResponse(url=request.url, body=self.driver.page_source, request=request, encoding='utf-8',
status=200) # 返回HTML数据
except TimeoutException:
return HtmlResponse(url=request.url, request=request, encoding='utf-8', status=500)
finally:
pass
在setting.py中打开下面的配置:
DOWNLOADER_MIDDLEWARES = {
'gp.middlewares.ChromeDownloaderMiddleware': 543,
}
参考文档链接如下:
Scrapy+Selenium+Headless Chrome的Google Play爬虫
为啥我chrome游览器关闭了,进程还在,不止一个呢
(2) 配置代理IP
chromedriver配置http代理IP代码样例如下:
from selenium import webdriver
chromeOptions = webdriver.ChromeOptions()
# 设置代理
chromeOptions.add_argument("--proxy-server=http://202.20.16.82:10152")
# 一定要注意,=两边不能有空格,不能是这样--proxy-server = http://202.20.16.82:10152
browser = webdriver.Chrome(chrome_options = chromeOptions)
# 查看本机ip,查看代理是否起作用
browser.get("http://httpbin.org/ip")
print(browser.page_source)
# 退出,清除浏览器缓存
browser.quit()
配置socks5代理IP配置信息如下,实际使用时替换上述代码中相应内容即可:
--proxy-server="SOCKS5://202.20.16.82:10152
参考链接如下:
selenium+python设置爬虫代理IP的方法
更专业的代理IP配置请参考下面链接:
selenium爬虫使用代理情况下不设置这几个参数,代理就白加了
如果不使用chromedriver,直接配置代理IP,则相应代码样例如下,但当前原生只支持http的代理IP,不支持socks5的代理IP。
# 调整spider中的代码
def start_requests(self):
request = scrapy.Request(url=self.url, callback=self.parse, dont_filter=True)
request.meta['proxy'] = 'http://202.20.16.82:10152'
yield request
可以使用pproxy曲线实现socks5代理,相关使用说明如下:
# 安装使用步骤
$ pip3 install pproxy
$ pproxy -l http://:8181 -r socks5://127.0.0.1:9150 -vv
# middlewares.py文件中调整
class ProxyMiddleware(object):
def process_request(self, request, spider):
request.meta['proxy'] = "http://127.0.0.1:8181"
# settings.py中放开下面内容
DOWNLOADER_MIDDLEWARES = {
'tutorial.middlewares.TutorialDownloaderMiddleware': 543,
}
参考链接如下:
How can proxy scrapy requests with Socks5?
五、进一步扩展
考虑到python的GIL限制,单个python进程只能多线程共享多核CPU中的单核,如果scrapy多线程无法满足需求,则可以考虑使用Scrapyd,下面是官方描述:
Scrapyd is an application (typically run as a daemon) that listens to
requests for spiders to run and spawns a process for each one, which
basically executes:scrapy crawl myspider
Scrapyd also runs multiple processes in parallel, allocating them in a
fixed number of slots given by the max_proc and max_proc_per_cpu
options, starting as many processes as possible to handle the load.In addition to dispatching and managing processes, Scrapyd provides a
JSON web service to upload new project versions (as eggs) and schedule
spiders. This feature is optional and can be disabled if you want to
implement your own custom Scrapyd. The components are pluggable and
can be changed, if you’re familiar with the Twisted Application
Framework which Scrapyd is implemented in.
如有需求,可进一步研究,未实际验证尝试,在此就不多说了。官方链接如下:
https://scrapyd.readthedocs.io/en/latest/
参考链接如下:
python全局解释器锁(GIL)
Using python scrapy crawl Web pages by multithreading [closed]