摘要:目录前言创建项目创建创建解析付费榜运行爬取初始列表调用脚本获取详情前言熟悉之后,本篇文章带大家爬取七麦数据的付费应用排行榜前名应用。根据传入的正则表达式对数据进行提取,返回字符串列表。
目录
前言
创建项目
创建Item
创建Spider
解析付费榜
运行爬取初始app列表
Selenium调用JS脚本
获取app详情
前言熟悉Scrapy之后,本篇文章带大家爬取七麦数据(https://www.qimai.cn/rank )的ios appstore付费应用排行榜前100名应用。
爬取内容包括app在列表中的下标,app图标地址,app的名称信息,app的类型,在分类中的排行,开发者,详情等。
考虑的问题:
Forbidden by robots.txt的错误
网页返回403
页面通过动态渲染,普通的请求url,在页面渲染之前已经返回response,解析没有数据
列表一页20个app,想要拿到前100个需要翻页,但是翻页没有更改url,而是通过js动态加载
...
创建项目在需要放置项目的目录下,
> scrapy startproject qimairank
回车即可创建默认的Scrapy项目架构。
创建Item创建Item来存储我们爬取的app在列表中的下标,app图标地址,app的名称信息,app的类型,在分类中的排行,开发者,详情。
修改items.py,在下面增加
class RankItem(scrapy.Item): # 下标 index = scrapy.Field() # 图标地址 src = scrapy.Field() # app标题信息 title = scrapy.Field() # app类型 type = scrapy.Field() # 分类中的排行 type_rank = scrapy.Field() # 开发者 company = scrapy.Field() # 详情信息 info = scrapy.Field()创建Spider
在spiders目录下创建RankSpider.py,并创建class RankSpider,继承于scrapy.Spider。
import scrapy class RankSpider(scrapy.Spider): name = "RankSpider" start_urls = ["https://www.qimai.cn/rank"] def parse(self, response): pass
name:用于区别Spider,该名字必须是唯一的。
start_urls:Spider在启动时进行爬取的url列表,首先会爬取第一个。
def parse(self, response):得到url的response信息后的解析方法。
解析付费榜解析用的Selectors选择器有多种方法:
xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.
extract(): 序列化该节点为unicode字符串并返回list。
re(): 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。
下面我们用xpath()选择节点,xpath的语法可参考w3c的http://www.w3school.com.cn/xp... 学习,需要熟悉语法、运算符、函数等。
def parse(self, response): base = response.xpath( "//div[@class="ivu-row rank-all-item"]/div[@class="ivu-col ivu-col-span-8"][2]//ul/li[@class="child-item"]/div[@class="ivu-row"]") for box in base: # 创建实例 rankItem = RankItem() # 下标 rankItem["index"] = box.xpath(".//div[@class="ivu-col ivu-col-span-3 left-item"]/span/text()").extract()[0] # 图标地址 rankItem["src"] = box.xpath(".//img/@src").extract()[0] # app名称信息 rankItem["title"] = box.xpath(".//div[@class="info-content"]//a/text()").extract()[0] # app类型 rankItem["type"] = box.xpath(".//div[@class="info-content"]//p[@class="small-txt"]/text()").extract()[0] # 分类中的排行 rankItem["type_rank"] = box.xpath( ".//div[@class="info-content"]//p[@class="small-txt"]//span[@class="rank-item"]/text()").extract()[ 0] # 开发者 rankItem["company"] = box.xpath( ".//div[@class="info-content"]//p[@class="small-txt"]//span[@class="company-item"]/text()").extract()[ 0] # 详情页地址 infoUrl = "https://www.qimai.cn" + box.xpath(".//div[@class="info-content"]//a/@href").extract()[0] yield rankItem运行爬取初始app列表
直接运行
qimairank>scrapy crawl RankSpider -o data.json
你会发现窗口没有item输出,data.json中也没有数据,是我们写错了吗?
scrapy默认遵守robot协议的,在访问网址前会先访问robot.txt来查看自己是否有权限访问。如果网站不允许被爬,就不能访问。
怎么样不遵守协议呢?
settings.py # Obey robots.txt rules ROBOTSTXT_OBEY = False
再次运行仍然失败,我们来看下具体原因:
因为七麦网站对请求的User-Agent做了校验,解决办法是在配置文件
settings.py # Enable or disable downloader middlewares # See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html DOWNLOADER_MIDDLEWARES = { # "qimairank.middlewares.QimairankDownloaderMiddleware": 543, "qimairank.middlewares.RandomUserAgent": 1, } USER_AGENTS = [ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)", "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", "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", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20", "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", ]
并在middlewares.py中创建RandomUserAgent
import random class RandomUserAgent(object): """ 随机获取settings.py中配置的USER_AGENTS设置"User-Agent" """ def __init__(self, agents): self.agents = agents @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist("USER_AGENTS")) def process_request(self, request, spider): request.headers.setdefault("User-Agent", random.choice(self.agents))
再次运行,没有报错,但是没有数据,是我们的xpath写错啦?我们在parse中增加输出body的信息
可以看到body为空,没有我们需要的列表数据,这是因为七麦数据是通过js动态渲染的,在渲染完成前,我们的response已经返回,那么怎么样才能等一等呀,等到渲染完成才返回呢?
爬取动态渲染的方式,我知道是通过Splash或者Selenium,像我们的桌面版系统可以选择用Selenium,操作可以设置可视化,所有界面操作都能看见,Splash依赖于Docker,无界面。
安装Selenium包:
pip install selenium
使用前需要安装驱动,配置详情点击
驱动安装完成,在middlewares.py中创建 SeleniumMiddleware
class SeleniumMiddleware(object): def __init__(self): self.timeout = 50 # 2.Firefox--------------------------------- # 实例化参数对象 options = webdriver.FirefoxOptions() # 无界面 # options.add_argument("--headless") # 关闭浏览器弹窗 options.set_preference("dom.webnotifications.enabled", False) options.set_preference("dom.push.enabled", False) # 打开浏览器 self.browser = webdriver.Firefox(firefox_options=options) # 指定浏览器窗口大小 self.browser.set_window_size(1400, 700) # 设置页面加载超时时间 self.browser.set_page_load_timeout(self.timeout) self.wait = WebDriverWait(self.browser, self.timeout) def process_request(self, request, spider): # 当请求的页面不是当前页面时 if self.browser.current_url != request.url: # 获取页面 self.browser.get(request.url) time.sleep(5) else: pass # 返回页面的response return HtmlResponse(url=self.browser.current_url, body=self.browser.page_source, encoding="utf-8", request=request) def spider_closed(self): # 爬虫结束 关闭窗口 self.browser.close() pass @classmethod def from_crawler(cls, crawler): # 设置爬虫结束的回调监听 s = cls() crawler.signals.connect(s.spider_closed, signal=signals.spider_closed) return s
在settins.py中配置
# Enable or disable downloader middlewares DOWNLOADER_MIDDLEWARES = { # "qimairank.middlewares.QimairankDownloaderMiddleware": 543, "qimairank.middlewares.RandomUserAgent": 1, "qimairank.middlewares.SeleniumMiddleware": 10, }
再次运行scrapy crawl RankSpider -o data.json,啦啦啦~这回有数据啦。
Selenium调用JS脚本观察爬取出来的data.json,发现怎么肥四,只有20条数据,而且除了前6个的app图标都是七麦的默认图标。
这是因为七麦数据的列表默认每页20条,而且默认渲染前6个的图标,其余的页需要触发滑动事件加载,而且滑动到的图标才开始渲染。这样怎么办呢?我们只需要滑动到可以加载的按钮就可以啦,检查发现在三个列表的外层标签有一个class为cm-explain-bottom的标签
我们用Selenium调用js脚本,滑动到这个标签就可以啦,在中间件process_request方法更改
def process_request(self, request, spider): # 当请求的页面不是当前页面时 if self.browser.current_url != request.url: # 获取页面 self.browser.get(request.url) time.sleep(5) # 请求的url开始为https://www.qimai.cn/rank/时,调用滑动界面,每页20个,滑动4次 if request.url.startswith("https://www.qimai.cn/rank"): try: for i in (0, 1, 2, 3): self.browser.execute_script( "document.getElementsByClassName("cm-explain-bottom")[0].scrollIntoView(true)") time.sleep(4) except JavascriptException as e: pass except Exception as e: pass
再次执行scrapy crawl RankSpider -o data1.json,则可看见已经生成data1.json里面有100个item。
获取app详情详情页需要跟进url,我们在RankSpider#parse方法中,不用yield Item,而是yield Request就可以跟进。
# 详情页地址 infoUrl = "https://www.qimai.cn" + box.xpath(".//div[@class="info-content"]//a/@href").extract()[0] # yield rankItem yield Request(infoUrl.replace("rank", "baseinfo"), self.parseInfo, meta={"rankItem": dict(rankItem).copy()}, dont_filter=True)
解析的infoUrl替换"rank"字符串为"baseinfo"就可以访问app应用信息页,用meta传递item到下一个解析方法中,用软拷贝的方式,避免Item因为地址相同,内容覆盖。
self.parseInfo为指定这次请求的解析方法,
def parseInfo(self, response): print("基地址:" + response.url) if response.status != 200: return rankItem = response.meta["rankItem"] info = dict() base = response.xpath("//div[@id="app-container"]") if base.extract(): # try: # 描述 try: info["desc"] = base.xpath( ".//div[@class="app-header"]//div[@class="app-subtitle"]/text()").extract()[0] except Exception as e: print("无描述") # 开发商 info["auther"] = base.xpath( ".//div[@class="app-header"]//div[@class="auther"]//div[@class="value"]/text()").extract()[0] # 分类 info["classify"] = base.xpath( ".//div[@class="app-header"]//div[@class="genre"]//div[@class="value"]/a/text()").extract()[0] # appid info["appid"] = base.xpath( ".//div[@class="app-header"]//div[@class="appid"]//div[@class="value"]/a/text()").extract()[0] # appstore地址 info["appstorelink"] = base.xpath( ".//div[@class="app-header"]//div[@class="appid"]//div[@class="value"]/a/@href").extract()[0] # 价格 info["price"] = base.xpath( ".//div[@class="app-header"]//div[@class="price"]//div[@class="value"]/text()").extract()[0] # 最新版本 info["version"] = base.xpath( ".//div[@class="app-header"]//div[@class="version"]//div[@class="value"]/text()").extract()[0] # 应用截图 info["screenshot"] = base.xpath( ".//div[@class="router-wrapper"]//div[@class="app-screenshot"]//div[@class="screenshot-box"]//img/@src").extract() # 应用描述 info["desc"] = base.xpath( ".//div[@class="router-wrapper"]//div[@class="app-describe"]//div[@class="description"]").extract()[ 0] # 应用基本信息 info["baseinfo"] = [] for infoBase in base.xpath( ".//div[@class="router-wrapper"]//div[@class="app-baseinfo"]//ul[@class="baseinfo-list"]/li"): # print(info["baseinfo"]) try: info["baseinfo"].append(dict(type=infoBase.xpath(".//*[@class="type"]/text()").extract()[0], info=infoBase.xpath(".//*[@class="info-txt"]/text()").extract()[0])) except Exception as e: pass rankItem["info"] = info # 替换图标 列表加载为默认图标 rankItem["src"] = response.xpath("//*[@id="app-side-bar"]//div[@class="logo-wrap"]/img/@src").extract()[ 0] yield rankItem
再次执行scrapy crawl RankSpider -o data1.json,则可看见已经生成data2.json,但是生成的列表不是排行的列表,甚至是乱序的,原因是因为我们使用了url跟进返回,每个页面的请求返回的速度不一样,需要排序的话就写个小脚本按照index排个序。
项目源码
原文链接
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/45201.html
摘要:楚江数据是专业的互联网数据技术服务,现整理出零基础如何学爬虫技术以供学习,。本文来源知乎作者路人甲链接楚江数据提供网站数据采集和爬虫软件定制开发服务,服务范围涵盖社交网络电子商务分类信息学术研究等。 楚江数据是专业的互联网数据技术服务,现整理出零基础如何学爬虫技术以供学习,http://www.chujiangdata.com。 第一:Python爬虫学习系列教程(来源于某博主:htt...
摘要:时间永远都过得那么快,一晃从年注册,到现在已经过去了年那些被我藏在收藏夹吃灰的文章,已经太多了,是时候把他们整理一下了。那是因为收藏夹太乱,橡皮擦给设置私密了,不收拾不好看呀。 ...
摘要:学习网络爬虫主要分个大的版块抓取,分析,存储另外,比较常用的爬虫框架,这里最后也详细介绍一下。网络爬虫要做的,简单来说,就是实现浏览器的功能。 Python学习网络爬虫主要分3个大的版块:抓取,分析,存储 另外,比较常用的爬虫框架Scrapy,这里最后也详细介绍一下。 首先列举一下本人总结的相关文章,这些覆盖了入门网络爬虫需要的基本概念和技巧:宁哥的小站-网络爬虫,当我们在浏览器中输入...
摘要:所以如果对爬虫有一定基础,上手框架是一种好的选择。缺少包,使用安装即可缺少包,使用安装即可上一篇文章网络爬虫实战爬取相关库的安装的安装下一篇文章网络爬虫实战爬虫框架的安装 上一篇文章:Python3网络爬虫实战---9、APP爬取相关库的安装:Appium的安装下一篇文章:Python3网络爬虫实战---11、爬虫框架的安装:ScrapySplash、ScrapyRedis 我们直接...
摘要:快速入门首先,初步要做的就是快速构建一个爬虫。然后把结果加入到一个队列中。既然是入门,我们肯定是先关心我们需要的。 因为公司项目需求,需要做一个爬虫。所以我一个python小白就被拉去做了爬虫。花了两周时间,拼拼凑凑总算赶出来了。所以写个blog做个记录。 快速入门 首先,初步要做的就是快速构建一个爬虫。 配置环境 Mac下安装 1) 直接从官网下载 python下载官网 2) 是通过...
阅读 2619·2023-04-26 02:17
阅读 1582·2021-11-24 09:39
阅读 1052·2021-11-18 13:13
阅读 2465·2021-09-02 15:11
阅读 2751·2019-08-30 15:48
阅读 3371·2019-08-30 14:00
阅读 2389·2019-08-29 13:43
阅读 639·2019-08-29 13:07