过前七章的学习,相信大家对整个爬虫有了一个比较全面的了解 ,其中分别涉及四个案例:静态网页爬取、动态Ajax网页爬取、Selenium浏览器模拟爬取和Fillderapp爬取,基本涵盖了爬虫的大致套路。本文在此基础上进一步深耕,使用Scrapy框架构建QQ音乐分布式爬虫系统,更加接近搜索引擎技术。
1.Scrapy原理概述
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。其最初是为了爬虫或者数据采集所设计的, 也可以应用在获取API所返回的数据或者通用的网络爬虫。简单来说,与普通爬虫相比,Scrapy“有组织 有纪律”,更容易构建大规模抓取项目。
下图即为Scrapy框架的原理架构图,下面来一一解释:
有了上文对Scrapy组件的介绍,下面描述一下Scrapy运作流程:
2.Scrapy安装配置
接下来开始安装Scrapy,Scrapy已经支持python3,本文环境为win10+Anaconda3,实测安装没有出现问题。首先通过pip安装Scrapy:
pip install scrapy
之后进入到python命行并导入,如果没有出现报错则初步说明安装成功。
import scrapy
3.Scrapy入门测试
接着我们通过一个百度分布式爬虫框架小例子进行测试,首先在cmd中用cd命令切到任一目录,之后运行:
scrapy startproject littletest
然后切换至项目目录并通过genspider命令加入爬虫网站:
cd littletest
scrapy genspider baidu www.baidu.com
之后进入目录查看,目录结构如下:
同时我们进入settings.py将ROBOTSTXT_OBEY配置项改为False,即不遵守爬虫协议,否则很多网站无法正常获取。
ROBOTSTXT_OBEY = False
最后进入命令行启动scrapy爬虫:
scrapy crawl baidu
得到结果如下,状态码为200且接收字节数大于0,则表明爬取成功!
3.MongDB安装配置
MongoDB 是目前最流行的 NoSQL 数据库之一,使用的数据类型 BSON(类似 JSON),下载安装及配置以及链接python的pymongo数据库和最优秀的compass可视化工具安装及使用可参考作者博客。
二、QQ音乐爬虫实战
1.网页分析
通过打开QQ音乐官网并点击歌手栏(链接传送门:https://y.qq.com/portal/singer_list.html),并打开DevTools工具,选择XHR异步并观察item,发现musicu.fcg一栏返回的json数据中有歌手相关信息。
因此我们进一步进入该项headers获取到请求url,继续点击下一页,通过三页(url如下)查找规律进一步发现sin参数发生变化,规律公式为80*(n-1),n为页码。篇幅有限,json数据解析就不再解释,可参考前文。
https://u.y.qq.com/cgi-bin/musicu.fcg?-=getUCGI9874589974344781&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data=%7B%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%2C%22singerList%22%3A%7B%22module%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22index%22%3A-100%2C%22sin%22%3A0%2C%22cur_page%22%3A1%7D%7D%7D
https://u.y.qq.com/cgi-bin/musicu.fcg?-=getUCGI8205866038561849&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data=%7B%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%2C%22singerList%22%3A%7B%22module%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22index%22%3A-100%2C%22sin%22%3A80%2C%22cur_page%22%3A2%7D%7D%7D
https://u.y.qq.com/cgi-bin/musicu.fcg?-=getUCGI8189152987042585&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data=%7B%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%2C%22singerList%22%3A%7B%22module%22%3A%22Music.SingerListServer%22%2C%22method%22%3A%22get_singer_list%22%2C%22param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genre%22%3A-100%2C%22index%22%3A-100%2C%22sin%22%3A160%2C%22cur_page%22%3A3%7D%7D%7D
以此类推依次获取到歌曲下载地址、歌曲列表地址、歌词列表地址、歌曲评论地址等并配置翻页参数:
start_urls = ['https://u.y.qq.com/cgi-bin/musicu.fcg?data=%7B%22singerList%22%3A%7B%22module%22%3A%22Music.SingerListServer' \
'%22%2C%22method%22%3A%22get_singer_list%22%2C%22param%22%3A%7B%22area%22%3A-100%2C%22sex%22%3A-100%2C%22genr' \
'e%22%3A-100%2C%22index%22%3A-100%2C%22sin%22%3A{num}%2C%22cur_page%22%3A{id}%7D%7D%7D'] # 歌手地址
song_down = 'https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg?&jsonpCallback=MusicJsonCallback&ci' \
'd=205361747&songmid={songmid}&filename=C400{songmid}.m4a&guid=9082027038' # 歌曲下载地址
song_url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?singermid={singer_mid}&order=listen&num={sum}' # 歌曲列表地址
lrc_url = 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric.fcg?nobase64=1&musicid={musicid}' # 歌词列表地址
discuss_url = 'https://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg?cid=205360772&reqtype=2&biztype=1&topid=' \
'{song_id}&cmd=8&pagenum=0&pagesize=25' # 歌曲评论地址
之后我们开始建立scrapy爬虫程序。首先切换至个人目录下面开启项目:
scrapy startproject musicspyder
cd musicspyder
scrapy genspider qqmusic y.qq.com
2.spyder.py编写
接下来开始对Scrapy组件逐一完善,首先对主要爬虫程序qqmusic.py进行编写,在生成类中分别定义爬虫名、允许域名、爬取url等变量,并创建解析用户信息、歌曲信息、歌词信息、评论信息、url信息方法:
import json
import scrapy
from scrapy import Request
from musicspyder.items import QqMusicItem
class MusicSpider(scrapy.Spider):
name = 'qqmusic'
allowed_domains = ['y.qq.com']
start_urls = ['...']
song_down = '...'
song_url = '...'
lrc_url = '...'
discuss_url = '...'
# 生成请求并从配置中获取页数
def start_requests(self):
# 解析用户信息
def parse_user(self, response)
# 解析歌曲信息
def parse_song(self, response)
# 解析歌词信息
def parse_lrc(self, response)
# 解析评论信息
def parse_comment(self, response)
# 解析url信息
def parse_url(self, response)
3.items.py编写
之后对items.py进行编写,在QqMusicItem类中创建MongoDB集合名、id字段、歌手名字段、歌曲名字段、歌曲地址字段、歌词字段、评论字段等变量:
import scrapy
from scrapy import Field
class QqMusicItem(scrapy.Item):
# mongodb collection
collection = 'singer'
id = Field()
# 歌手名字字段
singer_name = Field()
# 歌曲名字段
song_name = Field()
# 歌曲地址字段
song_url = Field()
# 歌词字段
lrc = Field()
# 评论字段
comment = Field()
4.piplines.py编写
再对piplines.py进行编写,新增加IrcText类对歌词进行解析处理:
import json
import pymongo
import re
from scrapy.exceptions import DropItem
from musicspyder.items import QqMusicItem
# 默认pipline类
class QqMusicPipeline(object):
def process_item(self, item, spider):
return item
# 在pipline中新增类用于解析和清洗单词
class lrcText(object):
# 进行正则匹配获取的单词
def process_item(self, item, spider):
# 保存到Mongo数据库
class MongoPipline(object):
# 构造方法
def __init__(self, mongo_url, mongo_db):
# 从settings.py中获取Mongo rl和库
@classmethod
def from_crawler(cls, crawler):
# 存储处理
def process_item(self, item, spider):
# 关闭mongodb数据库
def close_spider(self, spider):
之后是middlewares.py代码编写,自定义my_useragent类,使用random库随机选择浏览器头:
import random
from scrapy import signals
# 默认中间件
class MusicspyderSpiderMiddleware(object):
@classmethod
def from_crawler(cls, crawler):
def process_spider_input(self, response, spider):
def process_spider_output(self, response, result, spider):
def process_spider_exception(self, response, exception, spider):
def process_start_requests(self, start_requests, spider):
def spider_opened(self, spider):
# 在中间件中加入useragent防爬
class my_useragent(object):
def process_request(self, request, spider):
user_agent_list = ['...','...',...]
user_agent = random.choice(user_agent_list)
request.headers['User_Agent'] = user_agent
6.settings.py编写
最后是settings.py编写,配置相应的爬取页数、爬取歌手歌曲数量、mongoDB的地址与数据库等变量,并且设置不遵守Robots协议,开启下载中间件和itempipline:
# 系统配置变量
BOT_NAME = 'musicspyder'
SPIDER_MODULES = ['musicspyder.spiders']
NEWSPIDER_MODULE = 'musicspyder.spiders'
MAX_PAGE = 3 # 爬取页数
SONGER_NUM = 1 # 爬取歌手歌曲数量
MONGO_URL = 'mongodb://localhost:27017/'
MONGO_DB = 'music' # mongo数据库
# 定义robots协议遵守规则为:不遵守
ROBOTSTXT_OBEY = False
# 启用下载中间件
DOWNLOADER_MIDDLEWARES = {
# 'musicspyder.middlewares.QqMusicDownloaderMiddleware': 543,
'musicspyder.middlewares.my_useragent': 544,
}
# 启用pipline中mongodb存储
ITEM_PIPELINES = {
# 'musicspyder.pipelines.QqMusicPipeline': 300,
'musicspyder.pipelines.lrcText': 300,
'musicspyder.pipelines.MongoPipline': 302,
}
定义上述scrapy组件完成之后我们即可在命令行中输入以下命令用以启用qqmusic爬虫框架:
scrapy crawl qqmusic
之后进入mongodb查看爬取结果即可得到响应歌手歌曲信息:
至此Scrapy框架爬取QQ音乐讲解完成,Python网络爬虫数据采集实战系列也随之结束,总体来说,爬虫是一种细致活,需要掌握固定的套路并且去努力寻找网络数据规律的蛛丝马迹方能爬取成功,同时也要量力而行,防止对对方服务器造成巨大负载或者己方投入产出不成正比。完整代码可以在头条号中私信“QQ音乐”获得,前文涉及的基础知识可参考下面链接:
爬虫所要了解的基础知识,这一篇就够了!Python网络爬虫实战系列
一文带你深入了解并学会Python爬虫库!从此数据不用愁
Python爬虫有多简单?一文带你实战豆瓣电影TOP250数据爬取!
一文弄清Python网络爬虫解析库!内含多个实例讲解
谁说同花顺很难爬?一文带你学会用Python爬取财经动态网页!
谁说京东商品很难爬?一文教你用Python构建电商网站爬虫!
Python网络爬虫实战之Fiddler抓包app!附代码
参考链接:
https://blog.csdn.net/qq_1290259791/article/details/82263014
https://www.jianshu.com/p/cecb29c04cd2
https://cuiqingcai.com/4380.html
们之前用scrapy爬取过某网的段子,还记得怎么爬取吗?
爬虫网站代码(spiders/duanzi.py):
import scrapy
# 这里需要导入items.py文件包
from duanziPro.items import DuanziproItem
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://duanzixing.com/段子/']
def parse(self, response):
# 数据解析名称和内容
article_list = response.xpath('/html/body/section/div/div/article')
for article in article_list:
title = article.xpath('./header/h2/a/@title').extract_first()
note = article.xpath('./p[2]/text()').extract_first()
# 实例化一个item类型对象,将解析到的数据存储到该对象中
item = DuanziproItem()
item['title'] = title
item['note'] = note
# 将item对象提交给管道
yield item
item.py
import scrapy
class DuanziproItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
note = scrapy.Field()
管道文件代码(pipelines.py):
class DuanziproPipeline:
def process_item(self, item, spider):
print(item)
执行工程,打印数据如下:
数据看到了,但这只是一页,那怎么爬取多页呢?
有的同学就说了,这还不简单,直接加fro循环。
但有没有想过,管道的对象传递是yield,fro循环肯定行不通。
所以,这里我们引入了一个新的方式:手动请求发送。
手动请求发送
scrapy手动发送请求代码:
yield scrapy.Request(url,callback)
callback:制定解析函数,用于解析数据。
yield scrapy.FormRequest(url,callback,formdata)
formdata:字典,请求参数
代码实现:
我们先来创建一个新的工程:
然后修改配置文件等等
好了后我们先分析下网站。
我们看到翻页后的网址是/段子/2/等,那么就可以定义一个url模板
# 通用的url模板
url = 'https://duanzixing.com/段子/%d/'
上面说了,需要一个callback函数,用于解析数据的实现。其实就是我们爬取一页时的数据解析函数parse:
def parse(self, response):
# 数据解析名称和内容
article_list = response.xpath('/html/body/section/div/div/article')
for article in article_list:
title = article.xpath('./header/h2/a/@title').extract_first()
note = article.xpath('./p[2]/text()').extract_first()
# 实例化一个item类型对象,将解析到的数据存储到该对象中
item = HandreqproItem()
item['title'] = title
item['note'] = note
# 将item对象提交给管道
yield item
第一页的数据是直接传递给了管道,爬取5页的话,我们需要一个递归!
我们先在函数外定义一个page_num:
# 因为第一页有了,所以我们从第二页开始
page_num = 2
然后我们写递归代码,让yield scrapy.Request(url,callback)执行指定次数:
# 直接写在parse函数里面。放在yield item下面
# 爬取剩下的4页
while self.page_num <= 5: # 结束递归的条件
new_url = format(self.url % self.page_num) # 其他页码对应的完整url
self.page_num += 1
# 对新的页码对应的url进行请求发送(手动请求GET发送)
yield scrapy.Request(url=new_url, callback=self.parse)
管道中(pipelines.py)我们直接打印item就可以了,主要是看效果
class HandreqproPipeline:
# 定义一个数值,记录接收item的次数。也就是记录打印了多少条数据
count = 0
def process_item(self, item, spider):
print(item)
self.count += 1
print(self.count)
完整代码:
import scrapy
from handReqPro.items import HandreqproItem
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xx.com']
start_urls = ['https://duanzixing.com/段子/']
# 通用url模板
url = 'https://duanzixing.com/段子/%d/'
page_num = 2
# 将段子网中所有页码对应的数据进行爬取(前五页)
def parse(self, response):
# 数据解析名称和内容
article_list = response.xpath('/html/body/section/div/div/article')
for article in article_list:
title = article.xpath('./header/h2/a/@title').extract_first()
note = article.xpath('./p[2]/text()').extract_first()
# 实例化一个item类型对象,将解析到的数据存储到该对象中
item = HandreqproItem()
item['title'] = title
item['note'] = note
# 将item对象提交给管道
yield item
while self.page_num <= 5:
new_url = format(self.url % self.page_num)
self.page_num += 1
yield scrapy.Request(url=new_url, callback=self.parse)
执行工程~我们就发现打印了60条数据,每页是12条,总共5页!
其他问题
# 父类方法:这个是该方法的原始实现
def start_requests(self):
for u in self.start_urls:
yield scrapy.Request(url=u, callback=self.parse)
重写start_requests(self)父类方法即可
def start_requests(self):
for u in self.start_urls:
yield scrapy.FormRequest(url,callback,formdata)
关注Python涛哥!学习更多Python知识!
绍
网页抓取是一种重要的技术,经常在许多不同的环境中使用,尤其是数据科学和数据挖掘。 Python在很大程度上被认为是网络抓取的首选语言,其原因在于Python的内嵌电池特性。 使用Python,您可以在大约15分钟内使用不到100行代码创建一个简单的抓取脚本。 因此,无论何种用途,网页抓取都是每个Python程序员必须具备的技能。
在我们开始动手之前,我们需要退后一步,考虑什么是网页抓取,什么时候应该使用它,何时避免使用它。
如您所知,网页抓取是一种用于从网站自动提取数据的技术。 重要的是要理解,网页抓取是一种从各种来源(通常是网页)中提取数据的粗略技术。 如果网站的开发人员足够慷慨地提供API来提取数据,那么访问数据将是一种更加稳定和健壮的方式。 因此,根据经验,如果网站提供API以编程方式检索其数据,请使用它。 如果API不可用,则只能使用网络抓取。
请务必遵守有关您使用的每个网站的网页抓取的任何规则或限制,因为有些网站不允许这样做。 有了这个清楚的认识,让我们直接进入教程。
在本教程中,我们将抓取http://quotes.toscrape.com/,这是一个列出着名作家名言的网站。
网页抓取管道
我们可以将web-scraping理解为包含3个组件的管道:
下载:下载HTML网页
解析:解析HTML并检索我们感兴趣的数据
存储:以特定格式将检索到的数据存储在本地计算机中
下载HTML
从网页中提取任何数据,从逻辑上讲,我们首先要下载它。 我们有两种方法可以做到这一点:
1.使用浏览器自动化库
您可以使用Selenium等浏览器自动化库从网页下载HTML。 Selenium允许您打开浏览器,比方说Chrome,并根据需要控制它。 您可以在浏览器中打开网页,然后使用Selenium自动获取该页面的HTML代码。
但是,这种方法有一个很大的缺点 - 它明显变慢。 原因是运行浏览器并在浏览器中呈现HTML的开销。 此方法仅应用于特殊情况 - 我们要抓取的内容在浏览器中使用JavaScript代码,或者要求我们单击按钮/链接以获取数据,Selenium可以为我们执行此操作。
2.使用HTTP库
与第一种方法不同,HTTP库(例如Requests模块或Urllib)允许您发送HTTP请求,完全不需要打开任何浏览器。 这种方法应该始终是首选,因为它比Selenium快得多。
现在让我告诉您如何使用Selenium和Requests库实现管道这个组件:
使用Requests
使用以下命令安装Requests模块:
现在您可以在代码中使用它,如下所示:
这里,对URL进行HTTP GET请求,这几乎与下载网页同义。 然后,我们可以通过访问requests.get方法返回的结果对象来获取页面的HTML源代码。
使用Selenium
您可以通过pip安装selenium模块:
在这里,我们首先创建一个表示浏览器的webdriver对象。 这样做会在运行代码的计算机上打开Chrome浏览器。 然后,通过调用webdriver对象的get方法,我们可以打开URL。 最后,我们通过访问webdriver对象的page_source属性来获取源代码。
在这两种情况下,URL的HTML源都作为字符串存储在page变量中。
解析HTML和提取数据
不必深入计算机科学理论,我们可以将解析定义为分析字符串的过程,以便我们可以理解其内容,从而轻松访问其中的数据。
在Python中,有两个库可以帮助我们解析HTML:BeautifulSoup和Lxml。 Lxml是一个比BeautifulSoup更低级的框架,我们可以在BeautifulSoup中使用Lxml作为后端,因此对于简单的HTML解析,BeautifulSoup将是首选的库。
但在我们深入分析之前,我们必须分析网页的HTML,看看我们想要抓取的数据是如何构建和定位的。只有当我们掌握了这些信息时,我们才能从解析的HTML中获取我们想要的信息。但幸运的是,我们不必在编辑器中打开源代码,并手动理解每个HTML元素并将其与渲染页面中的相应数据相关联。大多数浏览器都提供了一个检查器,比如Chrome的开发人员工具,它使我们只需单击它们即可快速查看任何元素的HTML代码。
要在Chrome中执行此操作,请在Chrome中打开网页,然后右键单击要抓取的数据,然后选择“检查”。在Firefox中,此选项称为Inspect Element - 这是在做相同的事情,但只是名称不同。
您会注意到Chrome窗口底部打开了一个窗格,其中包含您单击的元素的源代码。 浏览一下源代码,了解我们想要抓取的数据是如何在HTML代码中构建的。
经过一些检查后你可以理解,http://quotes.toscrape.com/上的每个引用都包含在一个带有class =“quote”属性的div中。 在该div中,引用的文本在class =“text”的范围内,作者的名称在class =“author”的小标签中。 当我们实际解析HTML并提取数据时,将需要此信息。
现在,让我们开始使用BeautifulSoup解析HTML页面。 但首先,我们必须安装它:
安装好之后,可以像下面这样在代码中调用:
首先,我们通过将页面传递给BeautifulSoup类构造函数来创建页面的解析版本。 如您所见,我们还将第二个参数html.parser传递给构造函数。 这是Beautiful Soup将用于解析传递给它的字符串的解析器的名称。 你可以使用我们之前谈到过的解析器lxml,因为你已经安装了Lxml库。
然后,我们提取包含class =“quote”的页面中的所有div标签,因为我们知道这些是包含引用的div。 为此,Beautiful Soup 4提供了find_all功能。 我们将标记名称和类名称传递给find_all函数,并返回满足条件的所有标记,即包含引用的标记。
这里需要注意的一件重要事情是,我们在这里使用树结构。 变量soup以及引用的每个元素都是树。 在某种程度上,引用的元素是较大的soup树的一部分。 无论如何,为避免进入不同的讨论,让我们继续。
我们知道引用的文本是带有class =“text”的span标记,而作者是带有class =“author”的小标记。 要从quote元素中提取它们,我们再次使用类似的函数find。 find函数使用与find_all函数相同的参数。 唯一的区别是它返回满足条件的第一个标记,而find_all返回标记列表。 此外,我们希望访问返回对象的text属性,该对象包含该标记中包含的文本。
因此,正如您在代码中看到的那样,我们遍历列表引用的所有元素,并提取引用文本和作者名称,将它们存储在名称为scraped的列表。 在控制台上打印时,已抓取的列表如下所示:
存储检索的数据
一旦我们获得了数据,我们就可以以任何我们想要的格式存储它,例如CSV文件,SQL数据库或NoSQL数据库。 严格来说,这一步不应算作抓取过程的一部分,但为了完整起见,我将简要介绍它。
我想说最流行的存储抓取数据的方法是将它们存储为CSV电子表格,所以我将简要介绍如何做到这一点。 我不会详细介绍,因为您应该参考官方的Python文档。 所以,不用多说,让我们看看代码。
我们可以看到,代码非常明显。 我们从打开的quotes.csv文件创建一个CSV编写器对象,然后使用writerow函数逐个写入引用。 很明显,writerow函数接受一个列表作为输入,然后将其作为一行写入CSV。
结论和后续步骤
本教程应该帮助您了解在学习自己实现简单的scraper时基本上是什么。 这种抓取应该足以实现简单的自动化或小规模数据检索。 但是如果你想有效地提取大量数据,你应该研究一下抓取框架,特别是Scrapy。 它可以帮助您使用几行代码编写非常快速,高效的scraper。 无论你使用什么样的框架,在那个闪亮的表面下面,框架也使用这些非常基本的抓取原则,所以理解本教程应该可以帮助你为开始抓取的探险建立基础知识。
英文原文:https://stackabuse.com/introduction-to-web-scraping-with-python/
译者:javylee
*请认真填写需求信息,我们会在24小时内与您取得联系。