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