预计阅读时间:10min
阅读建议:本篇为代码实现,建议收藏,业余时间慢慢研究。
解决痛点:很多同学对于爬虫会有一些疑惑,小火龙希望用简单的语言向你说明爬虫的基本原理,以及如何通过一段简单的代码实现,帮助你尽快上手,文章聚焦于爬虫初学者。
00
序言
上篇文章中,小火龙和大家分享了爬虫的基础原理,回看可戳『理论篇』。本篇,小火龙手把手带你码一个简单的爬虫,附上代码,感兴趣的同学可以自己试一试。
01
爬虫背景
近期,房贷五年期LPR下降,使得购房的贷款成本有所下降,因此想看看北京不同区域,现阶段(2022年5月)vs 疫情前(2019年5月)价格差异。数据来源于「58同城」。
02
爬虫代码
上篇文章『理论篇』中,分享了爬虫的一般流程,这里就不再冗余,直接上代码。
简单介绍一下代码结构,输入文件有四个,输出文件有两个(代码可以写在一个文件中,但介于功能解耦,因此拆分为多个,可重点关注「爬虫核心文件」)。
【输入】
运行主文件(run.py):吊起核心程序的入口文件。
代码语言:javascript复制# encoding:utf-8
from configparser import ConfigParser, ExtendedInterpolation
import logging
import sys
import os
FILE_PATH = os.path.dirname(os.path.realpath(__file__))
CONF_PATH = FILE_PATH '/conf/conf.ini'
# 引入当前文件所在目录下的文件
sys.path.append(FILE_PATH '/lib')
sys.path.append(FILE_PATH '/spider')
from InfoSpider import InfoSpider
# 创建log日志函数
def init_logger():
logger = logging.getLogger()
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log/log.txt")
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
# 创建conf文件函数
def init_conf(config_path):
cf = ConfigParser(interpolation=ExtendedInterpolation())
cf.read(config_path, encoding='utf-8')
return cf
if __name__ == '__main__':
logger = init_logger()
conf = init_conf(CONF_PATH)
# 爬取58数据
info_spider = InfoSpider(conf)
info_spider.getHouseInfo()
爬虫核心文件(InfoSpider.py):实现爬虫的核心功能。
代码语言:javascript复制#encoding:utf-8
import logging
import sys
import os
import urllib
import urllib.request, urllib.parse
import re
import time
import random
from GeneralObj import GeneralObj
FILE_PATH = os.path.dirname(os.path.realpath(__file__))
FILE_PATH_MAIN = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")
#引入当前文件所在目录下的文件
sys.path.append(FILE_PATH_MAIN '/lib')
logger = logging.getLogger(__name__)
obj = GeneralObj()
class InfoSpider(object):
def __init__(self, conf):
self.conf = conf
#随机获取ua、ip
ua_conf = self.conf.get('spider_58', 'ua')
self.ua_list = ua_conf.strip().split('\001')
ip_conf = self.conf.get('spider_58', 'ip')
self.ip_list = ip_conf.strip().split(',')
def getHouseInfo(self):
begin_time = time.time()
#获取细分区域网址
self.getHouseInfo_area()
#获取细分区域商圈网址
self.getHouseInfo_page()
#获取细分区域商圈数据
self.getHouseInfo_detail()
end_time = time.time()
cost_time = (end_time - begin_time)//60
logger.info('cost time(min) : %i' % cost_time)
print('cost time(min) : %i' % cost_time)
def getHouseInfo_area(self):
logger.info('begin1 getHouseInfo_area url')
print('begin1 getHouseInfo_area url')
main_url = 'https://www.58.com/fangjiawang/shi-2022-100/'
headers = {"User-Agent": random.sample(self.ua_list, 1)[0]}
req = urllib.request.Request(main_url, headers=headers)
data = urllib.request.urlopen(req).read()
data = data.decode('utf-8')
data_filter = re.findall('<ul class="sel-sec" data-v-4571decc>(.*?)</ul>', data)
data_list = re.findall('<a href="(.*?)"', data_filter[0])[1:]
obj.recordsToFile(data_list, './data/getHouseInfo_area.txt', type=1, delimiter="t", mode="w")
logger.info('finish1 getHouseInfo_area url')
print('finish1 getHouseInfo_area url')
def getHouseInfo_page(self):
logger.info('begin2 getHouseInfo_page url')
print('begin2 getHouseInfo_page url')
area_url_list = obj.fileToRecords('./data/getHouseInfo_area.txt', 1)
area_url_num = len(area_url_list)
area_url_counts = 1
for line in area_url_list:
headers = {"User-Agent": random.sample(self.ua_list, 1)[0]}
req = urllib.request.Request(line, headers=headers)
data = urllib.request.urlopen(req).read()
data = data.decode('utf-8')
data_filter = re.findall('<ul class="sel-thi" data-v-4571decc>(.*?)</ul>', data)
data_list = re.findall('<a href="(.*?)"', data_filter[0])[1:]
obj.recordsToFile(data_list, './data/getHouseInfo_page.txt', type=1, delimiter="t", mode="a ")
logger.info('getHouseInfo_page url progress: %i/%i' % (area_url_counts, area_url_num))
print('getHouseInfo_page url progress: %i/%i' % (area_url_counts, area_url_num))
time.sleep(2)
area_url_counts = 1
def getHouseInfo_detail(self):
logger.info('begin3 getHouseInfo_detail url')
print('begin3 getHouseInfo_detail url')
detail_url_list = obj.fileToRecords('./data/getHouseInfo_page.txt', 1)
detail_url_num = len(detail_url_list)
detail_url_counts = 1
for line in detail_url_list:
try:
address2022 = line
address2019 = line.replace('shi-2022-100', 'shi-2019-100')
headers = {"User-Agent": random.sample(self.ua_list, 1)[0]}
req2022 = urllib.request.Request(address2022, headers=headers)
req2019 = urllib.request.Request(address2019, headers=headers)
data2022 = urllib.request.urlopen(req2022).read()
data2019 = urllib.request.urlopen(req2019).read()
data2022 = data2022.decode('utf-8')
data2019 = data2019.decode('utf-8')
area_list = []
# 获取2022
data_bigarea = re.findall('<div class="m-t mt20" data-v-2a40ba50>2022(.*?)各板块二手房均价</div>', data2022)
area_list.append(data_bigarea[0])
data_area = re.findall('2022年(.*?)房价走势图', data2022)
area_list.append(data_area[0])
data_price_2022 = re.findall('2022年5月房价</b><span data-v-2a40ba50>(.*?)元/㎡', data2022)
area_list.append(data_price_2022[0])
# 获取2019
data_price_2019 = re.findall('2019年5月房价</b><span data-v-2a40ba50>(.*?)元/㎡', data2019)
area_list.append(data_price_2019[0])
obj.recordsToFile(area_list, './data/getHouseInfo_detail.txt', type=4, delimiter="t", mode="a ")
logger.info('getHouseInfo_detail progress: %i/%i content:%s' % (detail_url_counts, detail_url_num, area_list))
print('getHouseInfo_detail progress: %i/%i content:%s' % (detail_url_counts, detail_url_num, area_list))
time.sleep(2)
detail_url_counts = 1
except:
logger.info('getHouseInfo_detail progress: %i/%i NO CONTENT:%s' % (detail_url_counts, detail_url_num, line))
print('getHouseInfo_detail progress: %i/%i NO CONTENT:%s' % (detail_url_counts, detail_url_num, line))
time.sleep(2)
detail_url_counts = 1
continue
配置文件(conf.ini):放置经常需更改的变量(此处筛选部分)。
代码语言:javascript复制[spider_58]
ua = Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0) 01Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0) 01Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1
ip = 125.112.76.113,110.85.124.116
自定义函数文件(GeneralObj.py):放置用户自定义函数。
代码语言:javascript复制#encoding:utf-8
import logging
import sys, os
import time
import random
FILE_PATH = os.path.dirname(os.path.realpath(__file__))
#引入当前文件所在目录下的文件
sys.path.append(FILE_PATH '../data')
class GeneralObj(object):
def __init__(self):
self.logger = logging.getLogger(__name__)
#传入list,存储到本地文件
def recordsToFile(self, records, file_name, type=1, delimiter="t", mode="w"):
f = open(file_name, mode)
if type==1:
for line in records:
f.write(line "n")
f.close()
if type==2:
for line in records:
f.write(delimiter.join(line) "n")
f.close()
if type==3:
f.write(records)
f.close()
if type==4:
f.write(delimiter.join(records) "n")
f.close()
#读取本地文件,存入list
def fileToRecords(self, file_name, type=1, delimiter="t", mode="r"):
lists = []
f = open(file_name, mode)
if type==1:
for line in f.readlines():
lists.append(line.strip())
f.close()
return lists
if type==2:
for line in f.readlines():
lists.append(line.strip().split(delimiter))
f.close()
return lists
【输出】
日志文件(log.txt):记录运行日志,方便在出现问题时进行排查。
爬取结果文件(data.txt):放置爬取结果。
03
数据分析
以下为数据爬取后,北京城六区整体数据及各区域价格涨幅TOP5商圈。
以上就是本期的内容分享。