今天终于部署了第一个scrapy爬虫,内心感慨万千。上周一直沉浸于使用requests的简洁直观之中,对scrapy臃肿的体系非常头痛。把两周以来从零学起的小小心得陈列如下:
Requests 入门
我是学习莫烦的爬虫基础途中,突然就在python上开了窍。强烈推荐他的爬虫教程。
目前使用爬虫是为了下载没有全文阅读选项的小说,要把整本书的单个章节全部下载下来,合并成一个文档。
Requests的流程非常人性化:先将网页的内容抓取下来
html = requests.get(url, headers=headers).text
2. 使用BeautifulSoup解析网页内容
soup = BeautifulSoup(html, features='html.parser')
3. 在soup里面用html的标签选择想要的内容,即小说某章节内容
work_contents = soup.find('div', {'id':'chapters'}).get_text(separator='\n')
4. 到这一步爬虫功能已经完工。
5. 接下来是用普通的python语法将章节内容写到本地文件(这里保存为txt格式)
with open("chapter.txt, "w", encoding="utf-8") as f:
f.writelines(work_contents)
6. 下载小说单章内容的代码已经全部完成!简单、轻松、快捷!
7. 那么,要下载多个章节怎么办呢?很简单,用for loop将上面的全部代码包裹起来,依次循环,每次更新一个章节的地址(url)以下载该章内容,同时更新文本文档名字(创建一个新文本)以写入该章内容。
pages = 12
for page in range(1, pages+1): # for loop
# some updated code
url = url + str(page) # sample code for updating url
html = ...
soup = ...
work_contents = ...
with open(f"{page}.txt","w", encoding="utf-8") as f: # sample code for updating file name
f.writelines(work_contents)
time.sleep(10) # delay the next round for 10 sec, avoid scraping too fast
8. 为了减缓爬取速度,避免被网站封禁,在抓取每个网页之间人工加上了延迟,即上面代码中的time.sleep()功能。
9. 至此,下载小说全本章节的代码也完工了!
Requests + Selenium 进阶
想爬取的网页需要登录,遇到这种情况又怎么办呢?有两种办法:
使用requests.Session.post()功能在请求网页时发送用户名和密码,可以保存coockies以保持登录状态;
或者,使用第三方神器Selemium+Katalon Recorder在firefox上模拟登录,并把cookies传送到requests.Session()中,达到登录目的。
稍微介绍一下第二种方式的流程:在代码中首先载入Selenium driver,这里使用了Chrome浏览器来模拟。再将登录页面的url传入driver.get()。
driver = webdriver.Chrome()
driver.get(url)
2. 接着打开Firefox浏览器,开启Katalon Recorder的record(录制)功能。正常输入用户名、密码,点击登录按钮,等待页面跳转。(这一步在浏览器中操作)
3. 关闭Katalon Recorder录制,点击export(导出),复制类似下面代码到python中。
driver.find_element_by_xpath().click() # sample code
driver.find_element_by_xpath().send_keys(username)
driver.find_element_by_xpath().click()
driver.find_element_by_xpath().send_keys(password)
4. 这样就完成了Selenium登录的过程,接下来是将cookies传入requests.Session()中。
cookies = driver.get_cookies()
session = requests.Session()
for cookie in cookies:
session.cookies.set(cookie['name'], cookie['value'])
5. 之后可以正常使用session.get()来请求网页,同时保持登录状态。
Scrapy 高级
熟练使用requests一周以后,我想要一次性爬取数百个网页。有几个困难,首先是当前的代码没有断点续传功能,另外为了避免网站封禁,总体等待时长已经达到了三个小时以上。
这个时候,已经封装了各种功能的Scrapy包就体现出了它的优势。看一下这个感天动地的回答吧——为啥要用scrapy代替requests或者urllib2:自动限速
尊重网站的机器人条款
处理网页有优势
自动重试
导出文件
自动缓存
然而,比起requests线性的直观体验,scrapy的入门学习坡度更抖。这里有一篇非常完美的scrapy下载小说教程小白进阶之Scrapy第一篇。
操作scrapy的流程如下:安装scrapy。我的上一篇文章涵盖了这一部分。
pip install scrapy
2. 在终端中使用 scrapy startproject 命令创建新的scrapy文件夹
scrapy startproject myspider
在项目文件夹中,scrapy已经自带了数个python文件,其中:items - 负责保存网页内容
spiders - 负责抓取网页,在此目录下建一个新的python文件来写scrapy爬虫
pipelines - 负责导出文件到本地
settings - 控制爬虫速度、自动限速、以及其它附加功能
middlewares - 本地缓存等功能
由此可以看出,scrapy将requests的功能分开了,封装到不同的文件下,更加自动化。
3. 现在正式开始使用scrapy。首先,在Item中为想要抓取的内容命名。
class MySpiderItem(scrapy.Item):
title = scrapy.Field() # sample code
contents = scrapy.Field()
pass
4. 接着,在spiders文件夹下创建一个新的python文件,例如myspider,py。在此处开始写爬虫。
首先为爬虫命名:
class MySpider(scrapy.Spider):
name = "myspider"
接着,如果是由一个或数个网址开始往下爬,则写入start_urls(是一个列表[])。
class MySpider(scrapy.Spider):
name = "myspider"
start_urls = ["url"]
假设我一共有5个网页要爬,每个网页又有数个链接,最终目标是抓取5个页面上的每个链接的内容。
我希望避免手写列表,而用for loop来组成起始网址(就像requests的做法一样)。那么,
page = 5
class MySpider(scrapy.Spider):
name = "myspider"
bash_url = url
def start_requests(self):
for i in (1, page+1):
url = self.base_url + str(i)
yield Request(url, self.parse)
这里,最后一行的yield会持续抛出Request()对当前url的解析内容(即response),然后用一个回调函数传入parse方法中。
我们接着来看parse方法。接着往下写。
def parse(self, response):
atags = BeautifulSoup(response.text, 'lxml').find_all('a', {'class':'name'})
urls = [l['href'] for l in atags]
for url in urls:
yield Request(url, callback=self.get_content)
现在,parse首先用BeautifulSoup分析了传过来的网页内容。先提取了页面上所有符合条件的标签。接着用该语句提取标签内部的url。
urls = [l['href'] for l in atags]
现在我们已经提取了单个页面上所有符合条件的url。针对每一个url,我们继续用yield抛出Request()对这些新的url的解析,其后传入下一个方法get_content()中,抓出新页面的内容。
下面是get_content()方法示例。
def get_content(self, response):
soup = BeautifulSoup(response.text, 'lxml')
item = ScrapyTeaItem()
item['title'] = soup.find('p', {'class':'family'}).get_text()
item['contents'] = soup.find('p', {'class':'name'}).get_text()
return item
看到这里,myspider.py终于和items.py联系上了。至此,爬虫已经爬完了五个网页上所有链接页面的内容,并将抓取的内容输入到items里面。
5. 现在,我们可以打开pipelines来存储items中的内容了。不过,要启用pipelines功能,我们得先打开settings.py文件,删除ITEM_PIPELINES = {前面的#号注释。
回到pipelines文件。将抓取的网页内容写入本地txt文件的代码如下。
class MySpiderPipeline(object):
def process_item(self, item, spider):
with open('myspider.txt', 'a', encoding='utf-8') as f: # "a" for append mode
f.write('\n'+ item['title'] + '\n') # one line
f.writelines(item['contents']) # multiple lines
return item
这样,保存网页内容的代码也写好了。
6. 如果想要控制爬取速度,则在settings中设置:要打开自动限速,则取消掉AUTOTHROTTLE前的#注释
要人为限速,或者设置自动限速的最低值,取消DOWNLOAD_DELAY前的#注释
7. 现在开始运行我们所写的爬虫。在终端中输入 scrapy crawl myspider 即可运行,创建的txt文档会保存在此项目的根目录下。
至此,scrapy爬虫已经成功写好!
Scrapy Cloud
scrapy还提供云端爬虫服务,免费用户可以一次运行一个爬虫。
2. 点击CREATE PROJECT(创建项目),输入项目名称
3. 进入Deploy Management页面,这里介绍Command line使用终端将本地项目传到云端
4. 在本地项目文件夹的终端中(在终端中使用cd和cd...命令来前进或后退文件夹),运行代码。
pip install shub
安装完全以后,再运行
shub login
在提示的API KEY处,输入云端页面上的API键
然后使用以下语句将本地项目推送到云端
shub deploy #加上云端页面上的id号码
如果使用BeautifulSoup,将会报错bs4包不支持
解决任何不支持的包的方法是这样的:本地已经自动新建了一个文件名为scrapinghub.yml。打开这个文件,在project下面添加。
project:
requirements:
file: requirements.txt
2. 在本项目根目录(即,scrapinghub.yml同级)创建文本文件requirements.txt
3. 打开requirements.txt,写入出问题的包的名字、版本,按照以下格式写。
beautifulsoup4==4.7.1
4. 要查看包的具体版本,在终端中输入pip show。例如。
pip show beautifulsoup4
5. 保存所有文件,再次在终端中推送项目。
shub deploy
这次应该可以成功了!
在云端运行爬虫:选择项目页面上的Run(运行)即可。
注意:
因内容长度关系,本文没有提及代码开头需要载入的python包。实际运行中如果没有载入相应的包,python会报错。
BeautifulSoup中为抓取网页内容所使用的标签也仅为示例,不同网站请使用浏览器的View Source和Inspect功能来查看具体标签。
bs4运行较慢,高级选择建议使用xpath或者scrapy selector来选取标签。
selenium速度也很慢,作为网站调试工具,会载入过多不相干的网页内容。
请保持良好的爬虫礼仪,尊重网站机器人条款、勿高速爬取造成服务器负担、爬取的内容勿做非法用途、请勿侵权。
学习之途漫漫,与君共勉!