2023数据采集与融合技术实践第三次作业
作业①:
要求:
指定一个网站,爬取这个网站中的所有的所有图片,例如:中国气象网(http://www.weather.com.cn)。使用scrapy框架分别实现单线程和多线程的方式爬取。
–务必控制总页数(学号尾数2位)、总下载的图片数量(尾数后3位)等限制爬取的措施。
输出信息:
将下载的Url信息在控制台输出,并将下载的图片存储在images子文件中,并给出截图。
代码:
单线程
MySpider.py
class MySpider(scrapy.Spider):
name = 'MySpider'
start_urls = ['http://www.weather.com.cn/']
def parse(self, response):
src = response.xpath('//img/@src').extract()
img = Work1Item(src=src)
yield img
pipelines.py
class Work1Pipeline2:
def process_item(self,item,spider):
urls = item.get('src')
num = 1
for url in urls:#遍历图片并保存
filename = 'D:/DATASET/images/' + str(num) + '.jpg'
print('第'+str(num)+'张图片:'+url)
urllib.request.urlretrieve(url=url,filename=filename)
num+=1
return item
settings.py
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
IMAGES_STORE = r'D:\DATASET\images'
ITEM_PIPELINES = {
'work1.pipelines.Work1Pipeline2': 300,
}
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.16 Safari/537.36',
}
多线程
在MySpider.py加上如下语句
def __init__(self, *args, **kwargs):
super(MySpider, self).__init__(*args, **kwargs)
self.executor = ThreadPoolExecutor(max_workers=4)
def process_request(self, request, spider):
# 利用线程池异步发送请求
self.executor.submit(spider.crawler.engine.download, request, spider)
在settings.py加上如下语句
DOWNLOAD_DELAY = 0
CONCURRENT_REQUESTS = 100
CONCURRENT_REQUESTS_PER_DOMAIN = 100
CONCURRENT_REQUESTS_PER_IP = 100
COOKIES_ENABLED = False
输出结果:
由于天气网整个页面只有48张图片,于是爬取亚马逊网站
代码:
MySpider.py(有修改的部分)
for i in range(1,3):#学号后两位是02,于是爬取两页
url = f"https://www.amazon.cn/s?k=%E4%B9%A6%E5%8C%85&page={i}&__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&crid=1RAID9NTPCARM&qid=1698238172&sprefix=%E4%B9%A6%E5%8C%85%2Caps%2C154&ref=sr_pg_{i}"
start_urls.append(url)
def parse(self, response):
src = response.xpath('//img/@src').extract()
#print(src)
img = Work1Item(src=src)
yield img
pipelines.py
class Work1Pipeline2:
def __init__(self):
self.counter=1
def process_item(self,item,spider):
urls = item.get('src')
urls.pop()#去除最后一个元素
urls.pop(0)#去除第一个元素
#print(urls)
for url in urls:
filename = 'D:/DATASET/images/' + str(self.counter) + '.jpg'
print('第'+str(self.counter)+'张图片:'+url)
urllib.request.urlretrieve(url=url,filename=filename)
self.counter+=1
if(self.counter>102):#学号后三位是102,于是爬取102张图片
break
return item
输出结果:
心得和体会:
爬取天气网时,一开始使用正则表达式抓取图片的url,传输的数据类型在pipelines中下载的时候总是失败,即使用了循环,参数也还是一整个列表。于是转用xpath就便捷很多,可以使用xpath工具判断是否抓取成功。
爬取亚马逊网站时,在网页使用插件查找xpath是正确的图片的url,但是在本地运行时第一个元素和最后一个元素很奇怪
于是使用了pop函数去除这两个元素
在第一个代码里直接在循环中定义num在亚马逊网站中不可取,因为翻页过后会重新调用process_item函数,num又会从1开始计数。于是定义了self.counter
进行计数。
作业②:
要求:
熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取股票相关信息。
候选网站:东方财富网:https://www.eastmoney.com/
输出信息:
MySQL数据库存储和输出格式如下:
表头英文命名例如:序号id,股票代码:bStockNo……,由同学们自行定义设计
序号 股票代码 股票名称 最新报价 涨跌幅 涨跌额 成交量 振幅 最高 最低 今开 昨收
1 688093 N世华 28.47 10.92 26.13万 7.6亿 22.34 32.0 28.08 30.20 17.55
2……
代码:
MySpider.py
class MySpider(scrapy.Spider):
name = 'MySpider'
start_urls = []
for i in range(1,3):
url =f"http://45.push2.eastmoney.com/api/qt/clist/get?cb=jQuery1124030395806868839914_1696659472380&pn={i}&pz=20&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&fltt=2&invt=2&wbp2u=|0|0|0|web&fid=f3&fs=m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048&fields=f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f12,f13,f14,f15,f16,f17,f18,f20,f21,f23,f24,f25,f22,f11,f62,f128,f136,f115,f152&_=1696659472381"
start_urls.append(url)
global num
print("序号", "股票代码", "股票名称", "最新价", "涨跌幅", "涨跌额", "成交量", "成交额", "振幅", "最高", "最低", "今开", "昨收", chr(12288))
def getContent(html):
stocks = re.findall("\"diff\":\[(.*?)]", html)
print(stocks)
stocks = list(eval(stocks[0]))
print(stocks)
num = 0
result = []
for stock in stocks:
num += 1
daima = stock["f12"]
name = stock["f14"]
newprice = stock["f2"]
diefu = stock["f3"]
dieer = stock["f4"]
chengjiaoliang = stock["f5"]
chengjiaoer = stock["f6"]
zhenfu = stock["f7"]
max = stock["f15"]
min = stock["f16"]
today = stock["f17"]
yesterday = stock["f18"]
result.append(
[num, daima, name, newprice, diefu, dieer, chengjiaoliang, chengjiaoer, zhenfu, max, min, today,
yesterday])
#print(result)
return result
def parse(self, response):
stockdb = Work2Pipeline() # 创建数据库对象
stockdb.openDB(MySpider) # 开启数据库
html = response.text
stocks = MySpider.getContent(html)
print(stocks)
for stock in stocks:
item = Work2Item()
item['Num'] = stock[0]
item['daima'] = stock[1]
item['name']= stock[2]
item['newprice']= stock[3]
item['diefu']=stock[4]
item['dieer']=stock[5]
item['chengjiaoliang']=stock[6]
item['chengjiaoer']=stock[7]
item['zhenfu']=stock[8]
item['max']=stock[9]
item['min']=stock[10]
item['today']=stock[11]
item['yesterday']=stock[12]
print(stock[0], stock[1], stock[2], stock[3], stock[4], stock[5], stock[6], stock[7], stock[8],stock[9], stock[10], stock[11], stock[12], chr(12288))
stockdb.process_item(item,MySpider)
yield item
pipelines.py
class Work2Pipeline:
def openDB(self,spider):
self.con = sqlite3.connect("stocks.db")
self.cursor = self.con.cursor()
try:
self.cursor.execute("create table if not exists stocks (Num varchar(16), stockCode varchar(16),stockName varchar(16),Newprice varchar(16),RiseFallpercent varchar(16),RiseFall varchar(16),Turnover varchar(16),Dealnum varchar(16),Amplitude varchar(16),max varchar(16),min varchar(16),today varchar(16),yesterday varchar(16))")
except:
self.cursor.execute("delete from stocks")
def process_item(self, item, spider):#插入数据
sql = "insert into stocks(Num,stockCode,stockName,Newprice,RiseFallpercent,RiseFall,Turnover,Dealnum,Amplitude,max,min,today,yesterday) values (?,?,?,?,?,?,?,?,?,?,?,?,?)"
params=list()
params.append(item['Num'])
params.append(['daima'])
params.append(item['name'])
params.append(item['newprice'])
params.append(item['diefu'])
params.append(item['dieer'])
params.append(item['chengjiaoliang'])
params.append(item['chengjiaoer'])
params.append(item['zhenfu'])
params.append(item['max'])
params.append(item['min'])
params.append(item['today'])
params.append(item['yesterday'])
self.cursor.execute(sql,tuple(params))
self.con.commit()
return item
def closeDB(self,spider):
self.con.commit()
self.con.close()
输出结果:
心得和体会:
这次试验和前一次做的差不多,因为网站是采用动态加载的,所以需要通过抓包获取资源。难点在于如何在scrapy框架下将数据存入数据库。
和前一次实验比较不一样的地方:
1、pipelines.py中定义创建数据库和关闭数据库的函数需要传入参数spider
2、process_item函数(其实就是插入数据)需要传入数据封装后的item和spider
一开始使用
sql = "insert into stocks(Num,stockCode,stockName,Newprice,RiseFallpercent,RiseFall,Turnover,Dealnum,Amplitude,max,min,today,yesterday) values (?,?,?,?,?,?,?,?,?,?,?,?,?)"
self.cursor.execute(sql % (item['Num'],item['daima'],item['name'],item['newprice'],item['diefu'],item['dieer'],item['chengjiaoliang'],item['chengjiaoer'],item['zhenfu'],item['max'],item['min'],item['today'],item['yesterday']))
会报错TypeError: not all arguments converted during string formatting
查询后是因为:%操作符只能直接用于字符串(‘123’),列表([1,2,3])、元组。
于是将函数修改为:(需要注意params添加的顺序要和sql语句插入的顺序一致,否则会出现错位甚至插入失败的情况)
sql = "insert into stocks(Num,stockCode,stockName,Newprice,RiseFallpercent,RiseFall,Turnover,Dealnum,Amplitude,max,min,today,yesterday) values (?,?,?,?,?,?,?,?,?,?,?,?,?)"
params=list()
params.append(item['Num'])
params.append(['daima'])
params.append(item['name'])
params.append(item['newprice'])
params.append(item['diefu'])
params.append(item['dieer'])
params.append(item['chengjiaoliang'])
params.append(item['chengjiaoer'])
params.append(item['zhenfu'])
params.append(item['max'])
params.append(item['min'])
params.append(item['today'])
params.append(item['yesterday'])
self.cursor.execute(sql,tuple(params))
self.con.commit()
就可以正确把数据插入数据库了。
一开始控制台经常没有输出,检查后发现parse函数都没有进入,其实可能是一些书写有问题,可以在run.py中将LOG_ENABLED=False
设置为True,以便排查错误。parse函数里有任何错误都可能导致整个函数没有输出。
作业③:
要求:
熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取外汇网站数据。
候选网站:中国银行网:https://www.boc.cn/sourcedb/whpj/
输出信息:
Currency TBP CBP TSP CSP Time
阿联酋迪拉姆 198.58 192.31 199.98 206.59 11:27:14
代码:
MySpider.py
class MySpider(scrapy.Spider):
name = 'MySpider'
start_urls =["https://www.boc.cn/sourcedb/whpj/"]
def parse(self, response):
waihuidb = Work3Pipeline() # 创建数据库对象
waihuidb.openDB(MySpider) # 开启数据库
items = response.xpath('//tr[position()>1]')
for i in items:
item = Work3Item()
item['Currency'] = i.xpath('.//td[1]/text()').get()
item['TBP'] = i.xpath('.//td[2]/text()').get()
item['CBP']= i.xpath('.//td[3]/text()').get()
item['TSP']= i.xpath('.//td[4]/text()').get()
item['CSP']=i.xpath('.//td[5]/text()').get()
item['Time']=i.xpath('.//td[8]/text()').get()
print(item)
waihuidb.process_item(item,MySpider)
yield item
pipelines.py
class Work3Pipeline:
def openDB(self,spider):
self.con = sqlite3.connect("waihui.db")
self.cursor = self.con.cursor()
try:
self.cursor.execute("create table if not exists waihui (Currency varchar(16), TBP varchar(16),CBP varchar(16),TSP varchar(16),CSP varchar(16),Time varchar(16))")
except:
self.cursor.execute("delete from waihui")
def process_item(self, item, spider):
sql = "insert into waihui(Currency,TBP,CBP,TSP,CSP,Time) values (?,?,?,?,?,?)"
params=list()
params.append(item['Currency'])
params.append(item['TBP'])
params.append(item['CBP'])
params.append(item['TSP'])
params.append(item['CSP'])
params.append(item['Time'])
self.cursor.execute(sql,tuple(params))
self.con.commit()
return item
def closeDB(self,spider):
self.con.commit()
self.con.close()
输出结果:
心得和体会:
本实验和第二个实验差不多,但是在这个实验里强化了xpath的使用,需要注意的是可以打开LOG_ENABLED
,以免因为xpath写错而导致没有输出结果。