scrapy分页抓取网页(项目文件创建好怎么写爬虫程序?程序怎么做?)

优采云 发布时间: 2022-01-03 07:01

  scrapy分页抓取网页(项目文件创建好怎么写爬虫程序?程序怎么做?)

  项目文件创建好后,我们就可以开始编写爬虫程序了。

  首先需要在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

  可以看到以下信息:

  以上,基本的数据清洗过程已经完成。下一篇文章我们将对数据进行探索性分析。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线