【数据采集与融合技术】第三次大作业

【数据采集与融合技术】第三次大作业

「数据采集」实验三

一、作业①

1.1 题目

  • 要求:指定一个网站,爬取这个网站中的所有的所有图片,例如中国气象网(http://www.weather.com.cn)。分别使用单线程和多线程的方式爬取。(限定爬取图片数量为学号后3位)

  • 输出信息:

    将下载的Url信息在控制台输出,并将下载的图片存储在images子文件夹中,并给出截图。

1.2 代码及思路

​ 单线程:3/1-1.py · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

​ 多线程:3/1-2.py · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

1.2.1 确定要爬取的目标图片的Xpath路径

​ 进入目标网页的【高清图集】界面如下,每张图片点击进去都是一个子网页,子网页中的图片就是我们要爬取的目标

​ 用浏览器的F12功能审查元素如下,发现一个<'li class="child"'>tag对应一张图片

​ 因此我们的爬取思路是:先访问【高清图集】,在将这个页面下的所有子网页的链接保存下来。接着依次遍历子网页,保存每个子网页所有图片的url,最后统一下载这些图片到本地(方便计数)

1.2.2 访问【高清图集】获取子网页url

#设置起始URL和请求头
url = "http://p.weather.com.cn/"
headers = {"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47'}

#get方法发送请求报文,获取XML文档
resp = requests.get(url)  # 返回resquest.Response对象
# print(resp.status_code)
# print(resp.text)
resp.encoding = 'utf-8'
demo = resp.text
soup = BeautifulSoup(demo, "lxml")

#links为【高清图集】总页面下的子链接
links=soup.select('a[href$=".shtml"][target]')

1.2.3 访问每个子页面并获取子页面下所有图片的URL

#存储图片路径的空列表
srcs_str = []

#共爬取22个子链接,每个子链接最多爬取5张图
for i in range(22):
    count=0 #爬取图片计数器
    pic_url=links[2*i]["href"]
    #访问子链接获取子链接的XML文档
    rp=requests.get(pic_url)
    rp.encoding = 'utf-8'
    do = rp.text
    sp = BeautifulSoup(do, "lxml")
    #在子链接的XML文档下搜索图片的url
    pics=sp.select('img[src$=".jpg"]')
    for pic in pics:
        print(pic["src"])
        srcs_str.append(pic["src"])
        count+=1
        if count==5:
            break

1.2.4 单线程爬取

# 单线程保存图片到指定路径
path = r"C:\Users\qq203\Desktop\数据采集与融合技术\第三次实验\单线程"
cnt = 1
for url in srcs_str:
    resp = requests.get(url)  # 获取指定图片的二进制文本
    img = resp.content
    with open(path + '\\image%d_' % cnt + '.jpg', 'wb') as f:
        f.write(img)
        print("第%d张图片下载成功" % cnt)
    if cnt == 111:
        break
    cnt += 1

1.2.5 多线程爬取

为每个图片设置一个线程任务,并将其设置为前台进程

def Download(path,url,c):
    resp = requests.get(url)  # 获取指定图片的二进制文本
    img = resp.content
    with open(path + '\\image%d_' % (c+1) + '.jpg', 'wb') as f:
        f.write(img)
    print("第%d张图片下载成功" % (c+1))

try:
    threads=[]
    c=0
    for i in range(22):
        #构建soup对象
        count = 0
        pic_url = links[2 * i]["href"]
        rp = requests.get(pic_url)
        rp.encoding = 'utf-8'
        do = rp.text
        sp = BeautifulSoup(do, "lxml")
        pics = sp.select('img[src$=".jpg"]')
        for pic in pics:
            print(pic["src"])
            url=pic["src"]
            count += 1
            if count == 5:
                break
            #新建下载线程下载图像
            path=r"C:\Users\qq203\Desktop\数据采集与融合技术\第三次实验\多线程"

            if c<111:
                t=threading.Thread(target=Download,args=(path,url,c))
                t.setDaemon(False) #设置为前台进程
                t.start()
                threads.append(t)
                c+=1
            else:
                break

    for thread in threads:
        thread.join()
except Exception as err:
    print(err)

1.3 运行结果

1.3.1 单线程运行结果

可以看到单线程按顺序爬取和下载,非常直观。


1.3.2 多线程运行结果

可以看到多线程爬取图片url和下载图片是同步进行的,所以交叉输出,且按顺序进入进程池的任务不一定按顺序执行,但最后爬取的图片总数与单线程相同。

1.4 心得体会

● 单线程简单直观,多线程速度快但需要考虑进程执行顺序的问题,并且需要进行访问控制防止对一个路径的重复写入或读出。

● 不一定要手动判断元素的Xpath路径,可以利用浏览器的F12功能实现:右键点击目标元素,选择复制,可以看到复制选项下的【复制Xpath路径】,点击即可复制目标元素的Xpath路径

二、作业②

2.1 题目

  • 作业②

    • 要求:使用scrapy框架复现作业①。

    • 输出信息:

      同作业①

2.2 代码及思路

​ 总文件夹:3/demo · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

​ myScrapy:3/demo/demo/spiders/myScrapy.py · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

​ items:3/demo/demo/items.py · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

​ pipelines:3/demo/demo/pipelines.py · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

2.2.1 确定要爬取的目标图片的Xpath路径

​ 思路同题目一,先保存【高清图集】下每个子页面的路径,再依次遍历爬取每个页面上的图片,但scrapy的默认爬虫的start_urls只有一个初始爬取路径,因此我们采用scrapy.Request()方法主动发送爬取请求,可以实现在同一个Scrapy访问多个页面

其中子页面路径的Xpath如下

2.2.2 Items-items

将每个图片视为一个项,属性只有一个url

import scrapy

class pics(scrapy.Item):
    url = scrapy.Field()

2.2.3 Spider-myScrapy

​ 翻页功能主要是在Spider实现的

设置初始url,即为【高清图集】的url

name = 'myScrapy'
allowed_domains = ['p.weather.com.cn']
start_urls = ['http://p.weather.com.cn/tqxc/index.shtml']

​ 对start_urls调用parse方法,爬取子网页url,每爬取到一个子网页的url就用scrapy.Request(url,callback)方法爬取

def parse(self, response):
	#用XML文档构建Selector对象
	data = response.body.decode()
	selector = scrapy.Selector(text=data)
	#获取子网页链接
	links = selector.xpath("//div[@class='oi']/div[@class ='tu']/a/@href")
	srcs_str = []
	for i in range(len(links)):
		count = 0
		link=links[i].extract()
		yield scrapy.Request(url=link, callback=self.parse1)

​ 对子网页的url调用parse1方法,获取子网页图片的url,将url用Item传递给pipelines

def parse1(self,response):
	#用XML文档构建Selector对象
    data = response.body.decode()
    selector = scrapy.Selector(text=data)
    #获取图片路径
    pics_url=selector.xpath("//li[@class='child']/a[@class='img_back']/img/@src")
    for i in pics_url:
        url=i.extract()
        print(url)
        item=pics()
        item['url']=url
        yield item

2.2.4 pipelines-pipelines

​ 对Spider传递过来的每个Item,获取其url属性,并下载目的图片存入本地。

class DemoPipeline:
    def open_spider(self,spider):
        self.conut = 0 #启动时初始化计数器
    def process_item(self, item, spider):
        import requests #用requests库下载图片
        path=r"C:\Users\qq203\Desktop\数据采集与融合技术\第三次实验\Scrapy"
        url=item['url']
        resp = requests.get(url)  # 获取指定图片的二进制文本
        img = resp.content
        with open(path + '\\image%d_' % (self.conut+1) + '.jpg', 'wb') as f:
            f.write(img)
            print("第%d张图片下载成功" % (self.conut+1))
        self.conut+=1

2.3 运行结果

爬取图片到本地结果如下

2.4 心得体会

●当需要爬取多个页面时,可以在spider的start_urls中加入多个url,项目会依次爬取,也可以主动调用scrapy.Request(url,callback)方法实现 对其他页面的访问

●可以在Settings中设置:

​ 1、是否遵循robot协议

​ 2、是否启用pipeline

​ 3、添加请求头和cookies

●用scrapy框架实现翻页及图片的下载更为方便,通过此次实验,我对scrapy的使用有了进一步了解。

三、作业③

3.1 题目

  • 要求:爬取豆瓣电影数据使用scrapy和xpath,并将内容存储到数据库,同时将图片存储在

    imgs路径下。

  • 候选网站: https://movie.douban.com/top250

  • 输出信息

序号 电影名称 导演 演员 简介 电影评分 电影封面
1 肖申克的救赎 弗兰克·德拉邦特 蒂姆·罗宾斯 希望让人自由 9.7 ./imgs/xsk.jpg
2....

3.2 代码及思路

总文件夹:3/demo2 · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

mySpider:3/demo2/demo2/spiders/mySpider.py · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

items:3/demo2/demo2/items.py · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

pipelines:3/demo2/demo2/pipelines.py · 灰色/2019级数据采集与融合技术 - 码云 - 开源中国 (gitee.com)

3.2.1 翻页规律

在浏览器进行翻页,其中第四页如下

第六页如下

因为一页共25部电影,推断第i页url应该为"https://movie.douban.com/top250?start=(25*i)&filter="

之后观察页面源码,获取每个属性的Xpath规律,推断<'ol class="grid_view"'>下的每个<'li'>tag对应一部电影

3.2.2 Items-items

一部电影对应一个Item,属性如下

import scrapy

class MovieItem(scrapy.Item):
    seq=scrapy.Field() #排名
    name=scrapy.Field() #电影名称
    director=scrapy.Field() #导演
    actors=scrapy.Field() #主演
    quote=scrapy.Field() #简介
    score=scrapy.Field() #评分
    dyfm=scrapy.Field() #电影封面

3.2.3 Spider-mySpider

首先组织需要爬取页面的url放入start_urls

name = 'mySpider'
allowed_domains = ['movie.douban.com/top250']
url='http://movie.douban.com/top250'
start_urls=[]
for i in range(10):
    if i==0:
        start_urls.append(url)
    else:
        start_urls.append(url+"?start="+str(i * 25))

接下来依次爬取每部电影的每个属性,并将其作为Item的属性,封装好Item传给pipelines

def parse(self, response):
    print(self.start_urls)
    data = response.body.decode()
    selector = scrapy.Selector(text=data)
    print(selector)
    movies=selector.xpath('//div[@id="content"]//li')
    cnt=1
    for movie in movies:
        item=MovieItem()
        #序号
        item['seq']=cnt
        #电影名
        item['name']=movie.xpath('./div/div[@class="pic"]/a[@href]/img/@alt').extract_first()
        #导演+主演
        #导演和主演在同一行内
        text=movie.xpath('./div/div[@class="info"]/div[@class="bd"]/p[@class]/text()').extract_first()
        text=text.strip().replace('\xa0\xa0\xa0','')
        item['director']=re.split(r'导演: |主演: ',text)[1]
        item['actors']=re.split(r'导演: |主演: ',text)[2]
        #简介
        item['quote']=movie.xpath('./div/div[@class="info"]/div[@class="bd"]
        /p[@class="quote"]/span/text()').extract_first()
        #评分
        item['score'] = movie.xpath('./div/div[@class="info"]/div[@class="bd"]/
        div[@class="star"]/span[@class="rating_num"]/text()').extract_first()
        #电影封面
        item['dyfm']=movie.xpath('./div/div[@class="pic"]/a[@href]/img/@src').extract_first()
        yield item

3.2.4 pipelines-pipelines

首先设计设计库操作类,用于创建数据库,写入数据,关闭数据库

class MovieDB:
    def openDB(self):
        self.con = sqlite3.connect("movies.db") #建立数据库链接,若没有对应数据库则创建
        self.cursor = self.con.cursor() #建立游标
        try:
            self.cursor.execute("create table movies "
                                "(mSeq int(4),mName varchar(16),"
                                "mDirector varchar(32),mActors varchar(64),"
                                "mQuote varchar(32),mScore varchar(8),mDyfm varchar(64))")
        except:
            self.cursor.execute("delete from movies")

    def closeDB(self):
        self.con.commit()
        self.con.close()

    def insert(self, mSeq, mName, mDirector, mActors, mQuote, mScore, mDyfm):
        try:
            self.cursor.execute("insert into movies (mSeq,mName,mDirector,mActors,mQuote,mScore,mDyfm) values (?,?,?,?,?,?,?)",
                                (mSeq, mName, mDirector, mActors, mQuote, mScore, mDyfm))
        except Exception as err:
            print(err)

接着编写Demo2Pipeline类,其中open_spider函数在开始执行项目时会执行一次,创建数据库并把计数器置为1。close_spide类在结束项目时会执行一次,断开数据库链接

class Demo2Pipeline:
    def open_spider(self,spider):
        print("开始爬取")
        self.count=1
        self.db=MovieDB()
        self.db.openDB()

    def process_item(self, item, spider):
        #下载图片
        path=r"C:\Users\qq203\Desktop\数据采集与融合技术\第三次实验\demo2\imgs"
        url = item['dyfm']
        resp = requests.get(url)  # 获取指定图片的二进制文本
        img = resp.content
        fm_path='imgs\image%d_' % self.count + '.jpg'
        with open(path + '\\image%d_' % self.count + '.jpg', 'wb') as f:
            f.write(img)
            print("第%d张图片下载成功" % self.count)
        self.count += 1
        #保存到数据库
        self.db.insert(item['seq'],item['name'],item['director'],item['actors'],item['quote'],item['score'],fm_path)
        return item

    def close_spider(self, spider):
        self.db.closeDB()
        print("结束爬取")

3.3 运行结果

数据库存储如下,其中在【简介】字段存在NULL,经过调查发现这部电影本身就没有简介


3.4 心得体会

● 爬取出现问题时,不一定是自己的代码出现问题,也可能是网页源码本身具有问题

● 我爬取了十页的内容,十页的url也可以正常输出,因此本应该有250部电影的信息,但最后只有92部电影信息,不知道为什么

● Scrapy的pipelines本质上也是多线程,所以使用全局计数器时可能会出现重复计数的问题

posted @ 2021-10-28 20:42  暴走小铸币  阅读(88)  评论(0编辑  收藏  举报