scrapy分页抓取网页(项目文件创建好怎么写爬虫程序?程序怎么做?)
优采云 发布时间: 2022-04-11 20:00scrapy分页抓取网页(项目文件创建好怎么写爬虫程序?程序怎么做?)
项目文件创建完成后,我们就可以开始编写爬虫程序了。
首先需要在items.py文件中预先定义要爬取的字段信息的名称,如下图:
class KuanItem(scrapy.Item):<br /># define the fields for your item here like:<br />name = scrapy.Field()<br />volume = scrapy.Field()<br />download = scrapy.Field()<br />follow = scrapy.Field()<br />comment = scrapy.Field()<br />tags = scrapy.Field()<br />score = scrapy.Field()<br />num_score = scrapy.Field()<br />
这里的字段信息是我们之前在网页中定位的8个字段信息,包括:name代表app的名称,volume代表音量,download代表下载次数。在这里定义好之后,我们会在后续的爬取主程序中用到这些字段信息。
2.3.3.爬取主程序
创建kuan项目后,Scrapy框架会自动生成一些爬取的代码。接下来,我们需要在parse方法中添加网页抓取的字段解析内容。
class KuspiderSpider(scrapy.Spider):<br /> name = 'kuan'<br /> allowed_domains = ['www.coolapk.com']<br /> start_urls = ['http://www.coolapk.com/']<br /><br /> def parse(self, response):<br /> pass<br />
在首页打开Dev Tools,找到每个抓取索引的节点位置,然后使用CSS、Xpath、regular等方法提取解析。这些方法都是 Scrapy 支持的,可以随意选择。这里我们使用 CSS 语法来定位节点,但需要注意的是,Scrapy 的 CSS 语法与我们之前使用 pyquery 的 CSS 语法略有不同。让我们举几个例子来比较一下。
首先,我们定位到第一个APP的首页URL节点。可以看到URL节点位于class属性为app_left_list的div节点下的a节点中,其href属性就是我们需要的URL信息,这里是相对地址,拼接后就是完整的URL:。
然后我们进入Kuan详情页面,选择app名称并定位。可以看到app name节点位于class属性为.detail_app_title的p节点的文本中。
定位到这两个节点后,我们就可以使用CSS来提取字段信息了。下面是常规写法和Scrapy中写法的对比:
# 常规写法<br />url = item('.app_left_list>a').attr('href')<br />name = item('.list_app_title').text()<br /># Scrapy 写法<br />url = item.css('::attr("href")').extract_first()<br />name = item.css('.detail_app_title::text').extract_first()<br />
如您所见,要获取 href 或 text 属性,您需要使用 :: 来表示它。例如,要获取文本,请使用 ::text。 extract_first() 表示提取第一个元素,如果有多个元素,使用 extract() 。然后,我们可以参考解析代码写出8个字段的信息。
首先,我们需要在首页提取应用的URL列表,然后进入每个应用的详情页,进一步提取8个字段的信息。
def parse(self, response):<br /> contents = response.css('.app_left_list>a')<br /> for content in contents:<br /> url = content.css('::attr("href")').extract_first()<br /> url = response.urljoin(url) # 拼接相对 url 为绝对 url<br /> yield scrapy.Request(url,callback=self.parse_url)<br />
这里使用response.urljoin()方法将提取出来的相对URL拼接成一个完整的URL,然后使用scrapy.Request()方法为每个App详情页构造一个请求。这里我们传递两个参数:url和callback,url是详情页的URL,callback是回调函数,它将首页URL请求返回的响应传递给专门用于解析字段内容的parse_url()方法,如如下图:
def parse_url(self,response):<br /> item = KuanItem()<br /> item['name'] = response.css('.detail_app_title::text').extract_first()<br /> results = self.get_comment(response)<br /> item['volume'] = results[0]<br /> item['download'] = results[1]<br /> item['follow'] = results[2]<br /> item['comment'] = results[3]<br /> item['tags'] = self.get_tags(response)<br /> item['score'] = response.css('.rank_num::text').extract_first()<br /> num_score = response.css('.apk_rank_p1::text').extract_first()<br /> item['num_score'] = re.search('共(.*?)个评分',num_score).group(1)<br /> yield item<br /> <br />def get_comment(self,response):<br /> messages = response.css('.apk_topba_message::text').extract_first()<br /> result = re.findall(r'\s+(.*?)\s+/\s+(.*?)下载\s+/\s+(.*?)人关注\s+/\s+(.*?)个评论.*?',messages) # \s+ 表示匹配任意空白字符一次以上<br /> if result: # 不为空<br /> results = list(result[0]) # 提取出list 中第一个元素<br /> return results<br /><br />def get_tags(self,response):<br /> data = response.css('.apk_left_span2')<br /> tags = [item.css('::text').extract_first() for item in data]<br /> return tags<br />
这里,get_comment() 和 get_tags() 方法是分开定义的。
get_comment()方法通过正则匹配提取出volume、download、follow、comment这四个字段。正则匹配结果如下:
result = re.findall(r'\s+(.*?)\s+/\s+(.*?)下载\s+/\s+(.*?)人关注\s+/\s+(.*?)个评论.*?',messages)<br />print(result) # 输出第一页的结果信息<br /># 结果如下:<br />[('21.74M', '5218万', '2.4万', '5.4万')]<br />[('75.53M', '2768万', '2.3万', '3.0万')]<br />[('46.21M', '1686万', '2.3万', '3.4万')]<br />[('54.77M', '1603万', '3.8万', '4.9万')]<br />[('3.32M', '1530万', '1.5万', '3343')]<br />[('75.07M', '1127万', '1.6万', '2.2万')]<br />[('92.70M', '1108万', '9167', '1.3万')]<br />[('68.94M', '1072万', '5718', '9869')]<br />[('61.45M', '935万', '1.1万', '1.6万')]<br />[('23.96M', '925万', '4157', '1956')]<br />
然后使用result[0]、result[1]等提取4条信息,以volume为例,输出第一页的提取结果:
item['volume'] = results[0]<br />print(item['volume'])<br />21.74M<br />75.53M<br />46.21M<br />54.77M<br />3.32M<br />75.07M<br />92.70M<br />68.94M<br />61.45M<br />23.96M<br />
这样,第一页10个app的字段信息全部提取成功,然后返回到yield item generator,我们输出它的内容:
[<br />{'name': '酷安', 'volume': '21.74M', 'download': '5218万', 'follow': '2.4万', 'comment': '5.4万', 'tags': "['酷市场', '酷安', '市场', 'coolapk', '装机必备']", 'score': '4.4', 'num_score': '1.4万'}, <br />{'name': '微信', 'volume': '75.53M', 'download': '2768万', 'follow': '2.3万', 'comment': '3.0万', 'tags': "['微信', 'qq', '腾讯', 'tencent', '即时聊天', '装机必备']",'score': '2.3', 'num_score': '1.1万'},<br />...<br />]<br />
2.3.4.分页爬取
以上,我们已经爬取了第一页的内容,接下来需要遍历爬取全部610页的内容。这里有两个想法:
这里,我们分别编写这两个方法的解析代码。
第一种方法很简单,直接在parse方法后面继续添加下面几行代码即可:
def parse(self, response):<br /> contents = response.css('.app_left_list>a')<br /> for content in contents:<br /> ...<br /> <br /> next_page = response.css('.pagination li:nth-child(8) a::attr(href)').extract_first()<br /> url = response.urljoin(next_page)<br /> yield scrapy.Request(url,callback=self.parse )<br />
第二种方法,我们在第一个parse()方法之前定义一个start_requests()方法,批量生成610个页面url,然后传给scrapy.Request()方法中的回调参数。下面的 parse() 方法进行解析。
def start_requests(self):<br /> pages = []<br /> for page in range(1,610): # 一共有610页<br /> url = 'https://www.coolapk.com/apk/?page=%s'%page<br /> page = scrapy.Request(url,callback=self.parse)<br /> pages.append(page)<br /> return pages<br />
以上是所有页面的爬取思路。爬取成功后,我们需要进行存储。在这里,我选择存储在 MongoDB 中。不得不说MongoDB比MySQL方便多了。
2.3.5. 存储结果
在 pipelines.py 程序中,我们定义了数据的存储方式。 MongoDB的一些参数,比如地址和数据库名,需要单独存放在settings.py设置文件中,然后在pipelines程序中调用。
import pymongo<br />class MongoPipeline(object):<br /> def __init__(self,mongo_url,mongo_db):<br /> self.mongo_url = mongo_url<br /> self.mongo_db = mongo_db<br /> @classmethod<br /> def from_crawler(cls,crawler):<br /> return cls(<br /> mongo_url = crawler.settings.get('MONGO_URL'),<br /> mongo_db = crawler.settings.get('MONGO_DB')<br /> )<br /> def open_spider(self,spider):<br /> self.client = pymongo.MongoClient(self.mongo_url)<br /> self.db = self.client[self.mongo_db]<br /> def process_item(self,item,spider):<br /> name = item.__class__.__name__<br /> self.db[name].insert(dict(item))<br /> return item<br /> def close_spider(self,spider):<br /> self.client.close()<br />
首先我们定义一个MongoPipeline()存储类,里面定义了几个方法,简单解释一下:
from crawler()是一个类方法,用@class方法标识,这个方法的作用就是获取我们在settings.py中设置的这些参数:
MONGO_URL = 'localhost'<br />MONGO_DB = 'KuAn'<br />ITEM_PIPELINES = {<br /> 'kuan.pipelines.MongoPipeline': 300,<br />}<br />
open_spider() 方法主要执行一些初始化操作。该方法会在 Spider 打开时调用。
process_item() 方法是向 MongoDB 插入数据最重要的方法。
完成以上代码后,输入以下命令,启动整个爬虫的爬取和存储过程。如果在单机上运行,完成6000个网页需要大量时间,请耐心等待。
scrapy crawl kuan<br />
这里还有两点:
首先,为了减轻网站的压力,我们最好在每个请求之间设置几秒的延迟。您可以在 KuspiderSpider() 方法的开头添加以下代码行:
custom_settings = {<br /> "DOWNLOAD_DELAY": 3, # 延迟3s,默认是0,即不延迟<br /> "CONCURRENT_REQUESTS_PER_DOMAIN": 8 # 每秒默认并发8次,可适当降低<br /> }<br />
其次,为了更好的监控爬虫的运行,需要设置输出日志文件,可以通过Python自带的日志包实现:
import logging<br /><br />logging.basicConfig(filename='kuan.log',filemode='w',level=logging.WARNING,format='%(asctime)s %(message)s',datefmt='%Y/%m/%d %I:%M:%S %p')<br />logging.warning("warn message")<br />logging.error("error message")<br />
这里的level参数表示警告级别,严重程度从低到高依次为:DEBUG
添加 datefmt 参数对于在每个日志的前面添加特定时间很有用。
以上,我们已经完成了整个数据的采集。有了数据,我们就可以开始分析了,但在此之前,我们需要简单地清理和处理数据。
3.数据清洗
首先,我们从MongoDB中读取数据并将其转换为DataFrame,然后看一下数据的基础知识。
def parse_kuan():<br /> client = pymongo.MongoClient(host='localhost', port=27017)<br /> db = client['KuAn']<br /> collection = db['KuAnItem']<br /> # 将数据库数据转为DataFrame<br /> data = pd.DataFrame(list(collection.find()))<br /> print(data.head())<br /> print(df.shape)<br /> print(df.info())<br /> print(df.describe())<br />
从data.head()输出的前5行数据可以看出,除了score列是float格式,其他列都是object text类型。
comment、download、follow、num_score,五列数据中的部分行带有“百万”字尾,需要去掉,转为数值类型;体积列的后缀分别为“M”和“K”,为了统一大小,需要将“K”除以1024转换为“M”体积。
整个数据共有6086行x 8列,每列没有缺失值。
df.describe() 方法对 score 列进行基本统计。可以看出,所有app的平均分3.9分(5分制),最低分1.6分。最高分 4.8 分。
接下来,我们将以上列文本数据转换为数值数据。代码实现如下:
def data_processing(df):<br />#处理'comment','download','follow','num_score','volume' 5列数据,将单位万转换为单位1,再转换为数值型<br /> str = '_ori'<br /> cols = ['comment','download','follow','num_score','volume']<br /> for col in cols:<br /> colori = col+str<br /> df[colori] = df[col] # 复制保留原始列<br /> if not (col == 'volume'):<br /> df[col] = clean_symbol(df,col)# 处理原始列生成新列<br /> else:<br /> df[col] = clean_symbol2(df,col)# 处理原始列生成新列<br /><br /> # 将download单独转换为万单位<br /> df['download'] = df['download'].apply(lambda x:x/10000)<br /> # 批量转为数值型<br /> df = df.apply(pd.to_numeric,errors='ignore')<br /> <br />def clean_symbol(df,col):<br /> # 将字符“万”替换为空<br /> con = df[col].str.contains('万$')<br /> df.loc[con,col] = pd.to_numeric(df.loc[con,col].str.replace('万','')) * 10000<br /> df[col] = pd.to_numeric(df[col])<br /> return df[col]<br /><br />def clean_symbol2(df,col):<br /> # 字符M替换为空<br /> df[col] = df[col].str.replace('M$','')<br /> # 体积为K的除以 1024 转换为M<br /> con = df[col].str.contains('K$')<br /> df.loc[con,col] = pd.to_numeric(df.loc[con,col].str.replace('K$',''))/1024<br /> df[col] = pd.to_numeric(df[col])<br /> return df[col]<br />
至此,几列文本数据的转换就完成了。先来看看基本情况:
commentdownloadfollownum_scorescorevolume
计数
6086
6086
6086
6086
6086
6086
意思
255.5
13.7
729.3
133.1
3.9
17.7
标准
1437.3
98
1893.7
595.4
0.6
20.6
分钟
1
1.6
25%
16
0.2
65
5.2
3.7
3.5
50%
38
0.8
180
17
4
10.8
75%
119
4.5
573.8
68
4.3
25.3
最大
53000
5190
38000
17000
4.8
294.2
由此可以看出以下信息:
至此,基本的数据清洗流程已经完成。在下一篇文章中文章我们将对数据进行探索性分析。
转载于: