数据采集第三次大作业

作业一

内容

  • 要求:指定一个网站,爬取这个网站中的所有的所有图片,例如中国气象网( http://www.weather.com.cn )。分别使用单线程和多线程的方式爬取。(限定爬取图片数量为学号后3位)
  • 输出信息:将下载的Url信息在控制台输出,并将下载的图片存储在images子文件中,并给出截图。

代码&结果

分析中国气象网的html信息,发现图片的链接在img标签的‘src’属性值内,其他链接在a标签的‘href’属性里。

  • 单线程:
    使用requests+re匹配得到图片的链接,以及网页内的其他链接。对其他链接的遍历访问,爬取其中的图片。
    其中要注意检查,得到的某些图片的链接不是像“https://……”一样的标准链接,跳过这类链接。
def spider(url, headers):
    global count
    global urls
    if count > 132:  # 如果已下载132张,则返回
        return
    if url not in urls:  # 判断该链接是否爬取过,如果没有爬取过则加入到urls列表,并对它进行爬取
        urls.append(url)
        try:
            data = getHTMLText(url, headers)  # 获得页面信息
            links = re.findall(r'a href="(.*?)"', data, re.S)  # 正则匹配,得到页面中的网页链接
            photos = re.findall(r'img src="(.*?)"', data, re.S)  # 正则匹配, 得到图片的链接
            for i in range(len(photos)):
                if photos[i].startswith("http"):  # 判断图片链接是否符合标准
                    download(photos[i], headers)  # 将图片链接传入download函数进行图片下载
                if count > 132:  # 如果已下载132张图片,则停止爬取
                    break
            if count <= 132:  # 如果下载不满132张图片,则对新链接进行访问爬取图片
                for link in links:
                    spider(link, headers)
        except Exception as err:
            print(err)

写一个download函数,下载相应链接的图片,按爬取顺序命名。

def download(url, count):  # 下载图片
    if count > 132:  #  已下载132张则返回
        return
    try:
        data = getPhoto(url, headers)  # 获得图片信息
        with open('./images/%d%s' % (count, url[-4:]), 'wb') as f:  # 利用count和图片原本的扩展名对图片进行重命名
            f.write(data)  # 写入本地文件
            print("{}   {}".format(count, url))   # 在控制台打印出图片名与链接
    except Exception as err:
        print(err)

可以看到,图片按爬取的顺序下载完成。

  • 多线程:
    仍然使用requests+re,只是增加了线程的创建,对图片进行并发爬取下载,其他部分的代码与单线程相同。
try:
    data = getHTMLText(url, headers)  # 获得页面信息
    links = re.findall(r'a href="(.*?)"', data, re.S)  # 正则匹配,得到页面中的其他链接
    photos = re.findall(r'img src="(.*?)"', data, re.S)  # 正则匹配, 得到图片的链接
    for i in range(len(photos)):
        if photos[i].startswith("http"):  # 判断图片链接是否符合标准
            # 创建线程T,执行download函数,参数为图片链接和用以对图片命名的count值
            T = threading.Thread(target=download, args=(photos[i], count))
            T.setDaemon(False)  # 非守护线程
            T.start()  # 启动线程
            threads.append(T)  # 将线程T装入threads列表
            count += 1  # 爬取图片数量加1
            if count > 132:  # 如果已下载132张,则退出循环
                break
    if count <= 132:  # 如果未下载132张图片,则爬取网页中的其他链接
        for link in links:
            spider(link, headers)  # 对链接进行爬取
except Exception as err:
    print(err)

观察在控制台输出的下载完的图片信息,起先仍然顺序完成下载,排错发现创建线程时没有用args进行传参(语句如下),导致多线程不能并发执行。
T = threading.Thread(target=download(photos[i], count))
经过代码调整,多线程并发下载图片,下载完成顺序与爬取到的顺序不同。

心得

  • 通过练习,我巩固了网站的遍历、链接的寻找,也体会到多线程并发下载图片,大大加快了下载速度。

完整代码链接

https://gitee.com/cxqi/crawl_project/tree/master/实验3/作业1

作业二

内容

  • 要求:使用scrapy框架复现作业一。
  • 输出信息:同作业一。

代码&结果

对网页html信息分析同作业一,直接进行代码编写。

  • items.py
    编写数据项目类PhotoItem,保存图片信息
class PhotoItem(scrapy.Item):
    data = scrapy.Field()  # 图片二进制信息
    name = scrapy.Field()  # 图片名
    link = scrapy.Field()  # 图片链接
  • pipelines.py
    编写数据处理类PhotoPipeline,对数据进行下载。
class PhotoPipeline(object):
    count = 0
    imdir = "E:/example/exe3/images/"  # 图片保存文件
    print("下载:")
    def process_item(self, item, spider):
        try:
            impath = self.imdir + item["name"]  # 图片保存路径,通过item的name对图片进行命名
            with open(impath, 'wb') as f:
                f.write(item["data"])  # 下载图片至本地文件
                print("{}   {}".format(item["name"], item["link"]))  # 在控制台打印图片名和相应链接
        except Exception as err:
            print(err)
        return item
  • settings.py
    编写配置文件,设置不遵守爬虫协议
    ROBOTSTXT_OBEY = False # 不遵守爬虫协议,防止被反爬
    把数据推送到PhotoPipeline类中
ITEM_PIPELINES = {  # 打开管道
   'exe3.pipelines.PhotoPipeline': 300,
}
  • myspider.py
    网页爬取入口函数:
def start_requests(self):  # 爬虫入口函数,爬取完网页执行回调函数parse
  yield scrapy.Request(url=self.url, callback=self.parse, headers=self.headers)

具体图片链接、其他网页链接的提取,并生成两种网页请求,一种是对图片链接,执行回调函数imParse;一种是其他网页链接,执行回调函数parse,继续进行图片与其他网页链接的爬取。

def parse(self, response):
  try:
      data = response.body.decode()
      selector = scrapy.Selector(text=data)
      photos = selector.xpath("//img/@src").extract()  # 提取图片链接
      links = selector.xpath("//a/@href").extract()  # 提取网页中的其他链接,用以翻页
      for photo in photos:  # 对图片进行处理
          if photo.startswith("http") and self.count <= 132:
              # 判断图片链接是否标准,如果图片链接可以访问,则执行回调函数imParse进行图片数据;同时判断下载图片数量,不满132张继续进行图片下载
              yield scrapy.Request(url=photo, callback=self.imParse, headers=self.headers)
      if self.count <= 132:  # 如果下载图片数量不满132张,再进行网页内的链接的访问爬取
          for link in links:
              if link not in self.urls:  # 判断该链接是否已经爬取
                  # 如果未爬取,则将其加入已访问列表urls,再进行该网页访问爬取
                  self.urls.append(link)
                  yield scrapy.Request(url=link, callback=self.parse, headers=self.headers)
  except Exception as err:
      print(err)

再写一个函数,完成对图片链接的访问下载,生成数据项目类PhotoItem,推送给PhotoPipeline。

def imParse(self, response):
  if self.count > 132:  # 已下载132张则返回
      return
  try:
      item = PhotoItem()  # 创建一个PhotoItem对象
      item["data"] = response.body  # 图片数据为图片链接对应的页面信息
      item["name"] = str(self.count) + response.url[-4:]  # 对图片重命名,包括用计数count和该图片本身的后缀名进行命名
      item["link"] = response.url  # 图片链接
      yield item
      self.count += 1
  except Exception as err:
      print(err)

运行程序,控制台输出如下:

下载得到图片:

心得

通过练习,我巩固了scrapy框架爬取网页信息的做法,同时学习到了对于不同需求的信息处理,可以编写多个回调函数来实现。

完整代码链接

https://gitee.com/cxqi/crawl_project/tree/master/实验3/作业2

作业三

内容

  • 要求:爬取豆瓣电影数据使用scrapy和xpath,并将内容存储到数据库,同时将图片存储在imgs路径下。
  • 候选网站: https://movie.douban.com/top250
  • 输出信息:

代码&结果

  • items.py
    由输出信息得到我们要提取的一部电影的信息有:
class MovieItem(scrapy.Item):
    rank = scrapy.Field()  # 电影排名
    name = scrapy.Field()  # 电影名称
    director = scrapy.Field()  # 导演
    actor = scrapy.Field()  # 演员
    summary = scrapy.Field()  # 简述
    score = scrapy.Field()  # 电影评分
    photo = scrapy.Field()  # 电影封面
    link = scrapy.Field()  # 电影封面图片链接
  • myspider.py
    在网页的html信息中找到所需信息的所在位置,进行信息提取。
    这里要注意,在得到的电影列表内,对每个电影进行xpath定位,需要写成“.//”形式,不能忽略“.”,否则extract_first后只能爬取到每页的第一个电影。
movies = selector.xpath("//li/div[@class='item']")  # 一个网页内的所有电影
for movie in movies:
    rank = movie.xpath(".//div[@class='pic']/em/text()").extract_first()  # 电影排名
    name = movie.xpath(".//div[@class='pic']//img/@alt").extract_first()  # 电影名称
    p = movie.xpath(".//div[@class='bd']/p/text()").extract_first()  # 电影导演及主演信息
    summary = movie.xpath(".//p[@class='quote']/span/text()").extract_first()  # 电影简介
    score = movie.xpath(".//span[@class='rating_num']/text()").extract_first()  # 电影评分
    link = movie.xpath(".//div[@class='pic']//img/@src").extract_first()  # 电影封面图片链接

发现有些信息不能直接得到,电影导演和演员信息在一个标签内,对提取到的字符串再进行re匹配。

director = re.findall(r'导演: (.*?)\xa0', p)  # re匹配得到电影的导演
actor = re.findall(r'主演: (.*)', p)  # re匹配得到电影的主演

生成数据项目类MovieItem,要注意对爬取不到的某些信息进行处理,如设置成""。

item = MovieItem()  # 创建一个MovieItem对象
# 对数据进行处理,如果提取的信息不存在,就把None转换为""
item["rank"] = rank if rank else ""
item["name"] = name if name else ""
item["director"] = director[0] if director else ""
item["actor"] = actor[0] if actor else ""
item["summary"] = summary if summary else ""
item["score"] = score if score else ""
# 将item的photo设置为电影封面图片保存的本地文件目录
item["photo"] = "./imgs/" + name + ".jpg" if name else ""
item["link"] = link if link else ""
yield item  # 将item推送给pipelines.py下的数据管道

对于多页爬取,还要进行翻页规律探索,可以看出规律比较简单。


self.page += 25
if self.page <= 225:  # 翻页处理,共爬取10页
  url = self.start_url + "?start=" + str(self.page) + "&filter="
  yield scrapy.Request(url=url, callback=self.parse, headers=self.headers)
  • pipelines.py
    MoviePipeline进行数据库表的创建与信息插入。
def process_item(self, item, spider):
    try:
        if self.opened:
            # 把爬取到的电影信息存入数据库
            self.cursor.execute("insert into movies (序号, 电影名称, 导演, 演员, 简介, 电影评分, 电影封面)"
                                "values (?,?,?,?,?,?,?)", (item["rank"], item["name"], item["director"],
                                                          item["actor"], item["summary"], item["score"], item["photo"]))
            self.count += 1
    except Exception as err:
        print(err)
    return item

PhotoPipeline继承ImagesPipeline,对图片进行下载。

class PhotoPipeline(ImagesPipeline):  # 引入ImagePipeline通道

    def get_media_requests(self, item, info):  # 重写get_media_requests函数,将图片的url生成request请求
        yield scrapy.Request(url=item['link'], meta={'item': item})  # 利用meta参数将item项进行传输,以便重命名时读取item中的电影名称

    def file_path(self, request, response=None, info=None):
        item = request.meta['item']  # 上面的meta传递过来item

        # 图片重命名,request.url.split('/')[-1].split('.')[-1]得到图片后缀jpg,png
        image_name = item['name'] + '.' + request.url.split('/')[-1].split('.')[-1]
        # 图片下载目录
        filename = u'imgs/{0}'.format(image_name)
        return filename
    def item_completed(self, results, item, info):
        # 判断如果存在path,就将图片保存到这个path
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:  # 如果其中没有图片,则采用drop
            raise DropItem("Item contains no images")
        return item
  • settings.py
IMAGES_STORE = r'.'  # 设置图片保存地址
ROBOTSTXT_OBEY = False  # 不遵守爬虫协议,防止被反爬
ITEM_PIPELINES = { # 打开管道,分配给每个管道的整型值,确定了他们运行的顺序,数值越低优先值越高
   'exe3_2.pipelines.MoviePipeline': 100,
   'exe3_2.pipelines.PhotoPipeline': 200,
}
  • 结果

心得

通过练习,我掌握了除了作业二方法外,scarpy框架下另一种图片的下载方式。同时我也掌握了meta传参的方法,因为其中比较特别的要求是对图片重命名,需要利用meta在scrapy.Request里进行传参,以便重命名时获得图片对应的电影名称。

完整代码链接

https://gitee.com/cxqi/crawl_project/tree/master/实验3/作业3

posted @ 2021-10-31 01:10  削木梨  阅读(53)  评论(0编辑  收藏  举报