我用Python爬取了敦煌网

做跨境电商,产品的市场行情是非常关键的指标,无论是新品开发还是市场调研都有需求,那么今天我们就来做个敦煌网的产品价格与销量查询的工具。

一、基础版本

其实敦煌网是很客气的网站,基本上没有做针对的反爬措施,既然别人这么客气,我们也要懂得礼貌,做爬虫的同学都知道爬虫的基本礼仪。那就是该停就停,能在晚上没有太多人的时候运行就放在人少的时候,频率不要太高。不过还我们的工具,一开始就打算按照关键词进行爬取,所以对网站的负担不会太重,可以放心的使用。

话不多说,先上代码


import requests
from bs4 import BeautifulSoup
import re
from urllib.parse import quote_plus
import sys


def save_data(url,path='dhgate.csv',data=None):
    web_data = requests.get(url)
    soup = BeautifulSoup(web_data.text,'lxml')
    prices = []
    orders = []
    for item in soup.select('#proList .price'):
        m = re.search(r'(\d*.\d*) - (\d*.\d*)',item.text)
        if m:
            price = float(m.group(1))+float(m.group(2))
            prices.append(round(price/2,2))
        else:
            pass
    for item in soup.select('#proList .attribute'):
        m = re.search(r'Sold: (\d+)',item.text)
        if m:
            orders.append(m.group(1)) 
        else:
            orders.append(None)

    for price, order in zip(prices,orders):
        data = {
        'price': price,
        'order': order
        }
        print(data)
        with open(path,'a') as f:
            f.write('{},{}\n'.format(data['price'],data['order']))

def get_data(key_word,page_num):
    key_word = quote_plus(key_word)
    urls = ['http://www.dhgate.com/w/{}/{}.html'.format(key_word,str(i)) for i in range(page_num)]
    for url in urls:
        save_data(url,key_word+'.csv')

if __name__ == '__main__':
    key_word,page_num = sys.argv[1:3]
    get_data(key_word,int(page_num))

内容比较简单,为了让大家不至于看的太累,注释什么的大多被我删除了。
下面我们来简单的讲解下这段代码。
首先,我们导入要用的包:

import requests # requests包主要用来获取网页内容
from bs4 import BeautifulSoup # BeautifulSoup用来解释网页内容
import re # re包是用正则来辅助解析用
from urllib.parse import quote_plus # quote_plus用来处理关键词
import sys # sys用来获取命令行的参数

主要流程都在__main__里面,我们通过sys获取的关键词和页数,这里没有异常处理,其实应该对传入的参数进行异常处理下的。然后直接运行get_data函数获取我们所需的数据。我们直接在get_data函数里调用的save_data把数据存储到csv文件中。
这个是最早的版本,大概是在2016写的,现在运行还是能够成功。这个版本,只获取的价格与销量。

二、第一次重构

其实很早就想重构一下,一直没有动力,大概是2017年3月的时候,有朋友问我对标题是怎么做的,终于找到理由重构一下了, 简单重构了下,新代码比较丑,数据储存还有bug, 当时太晚了,就没优化了,实现了获取产品标题,价格,起订量,销量,好评,店铺地址,卖家名,店铺好评率的获取。


import requests
from bs4 import BeautifulSoup
import re
from urllib.parse import quote_plus
import sys
from numpy import mean


def save_data(url,path='dhgate.csv',data=None):
    web_data = requests.get(url)
    soup = BeautifulSoup(web_data.text,'lxml')
        info = []
    items = soup.find_all("div", "listitem")
    for item in items:
        title = item.find("h3").find("a").text # 标题
        price = item.find("li","price").text # 价格
        m = re.findall(r'(\d+\.*\d+)', price)
        price = mean(list(map(float, m))) # 计算均价
        attribute = item.find("ul", "attribute").text
        min_order = re.findall(r'Min. Order: (\d+)', attribute)[0] # 起订量
        order = re.findall(r'Sold: (\d+)', attribute)
        order = order[0] if len(order) > 0 else 0 # 订单量
        feedback = item.find("span","reviewnum")
        feedback = re.findall(r"\d+", feedback.text)[0] if feedback else 0
        seller = list(item.find("span","seller").stripped_strings)[-1]
        store_url = item.find("span","seller").find("a")['href']
        store_feedback = item.find("li","feedback")
        store_feedback = re.findall(r"\d+\.*\d+", store_feedback.text)[0] if store_feedback else 0
        data = {
            'title': title,
            'price': price,
            'min_order': min_order,
            'order': order,
            'feedback': feedback,
            'seller': seller,
            'store_url': store_url,
            'store_feedback': store_feedback
        }
        print(data)
        with open(path,'a') as f:
            f.write('{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\n'.format(
                data['title'],
                data['price'],
                data['min_order'],
                data['order'],
                data['feedback'],
                data['seller'],
                data['store_url'],
                data['store_feedback']
                ))


def get_data(key_word,page_num):
    key_word = quote_plus(key_word)
    urls = ['http://www.dhgate.com/w/{}/{}.html'.format(key_word,str(i)) for i in range(page_num)]
    for url in urls:
        save_data(url,key_word+'.csv')

if __name__ == '__main__':
    key_word,page_num = sys.argv[1:3]
    get_data(key_word,int(page_num))

老样子,简单讲解下,其实主体和第一次写的没有太大差别,主要是字段解析这里,多添加了一些内容:

for item in items:
        title = item.find("h3").find("a").text # 标题
        price = item.find("li","price").text # 价格
        m = re.findall(r'(\d+\.*\d+)', price)
        price = mean(list(map(float, m))) # 计算均价
        attribute = item.find("ul", "attribute").text
        min_order = re.findall(r'Min. Order: (\d+)', attribute)[0] # 起订量
        order = re.findall(r'Sold: (\d+)', attribute)
        order = order[0] if len(order) > 0 else 0 # 订单量
        feedback = item.find("span","reviewnum")
        feedback = re.findall(r"\d+", feedback.text)[0] if feedback else 0
        seller = list(item.find("span","seller").stripped_strings)[-1]
        store_url = item.find("span","seller").find("a")['href']
        store_feedback = item.find("li","feedback")
        store_feedback = re.findall(r"\d+\.*\d+", store_feedback.text)[0] if store_feedback else 0

BeautifulSoupcss selector其实还是很好用的,当然,解析速度是相当来说慢了点,不过影响不是太大。后面我们会用lxmlxpath来重构,速度会好很多。对于新手,或者前端不是太理解的人来说,做爬虫还是比较坑的,我的经验来说,做爬虫最好还是要懂点前端, 当然懂得越多越好,爬虫与前端的反爬虫一直是这样相爱相杀,所以你越了解你的敌人,你就越得心就手。

三、用类的思想做个小框架

前面的内容基本已经能满足我们的需求了,但是类的思想可以让我们做到解耦,功能模块更清晰。
先上一个scrapy的框架图来镇楼:

主要内容有scrapy引擎,scheduler调度器,itempipline数据处理,downloader下载,spiders爬虫程序。
我们就模仿这个框架做一个简单的:

main.py # 主程序
url_manager.py # url管理器
html_downloader.py # 下载器 相当于scrapy的downloader
html_parser.py # 网页解析器 scrapy的解析直接就是在spiders里
html_outputer.py # 数据处理器 相当于scrapy的item pipeline

有了这几个类,我们已经可以完成一个简单的框架了。main.py 里主要是保证任务的进行。

import url_manager
import html_downloader
import html_outputer
import html_parser


class SpiderMain(object):
    def __init__(self):
        self.urls = url_manager.UrlManager()
        self.downloader = html_downloader.HtmlDownloader()
        self.parser = html_parser.HtmlParser()
        self.outputer = html_outputer.HtmlOutputer()

    def craw(self, key_word, page_num):
        count = 1
        self.urls.build_url(key_word, int(page_num))
        while self.urls.has_new_url():
            try:
                new_url = self.urls.get_new_url()
                print(f"craw {count} : {new_url}")
                html_cont = self.downloader.download(new_url)
                new_data = self.parser.parse(new_url, html_cont)
                self.outputer.collect_data(new_data)
                count += 1
            except Exception as e:
                print("craw failed", e)
        self.outputer.to_csv()
        # return self.outputer.datas


if __name__ == "__main__":
    spider = SpiderMain()
    print(spider.craw("women dress", "2"))

主程序内容比较简单,导入相应的类,构建了一个爬虫主类,传入关键词和页数,爬虫就愉快的开始爬网了。
核心就在这个craw函数。

  1. 首先url管理器构建一个初始url,告诉爬虫从哪儿开始爬取。
  2. 然后爬虫开始看url管理器里有没有新的url,有就获取新的url,把新的url传入下载器进行下载。
  3. 然后下载器把下载的数据传入解析器进行解析。
  4. 数据处理器收集解析器解析出来的新数据。
  5. 数据处理器保存数据到本地。

下面我们一个一个讲解这几个功能类:

  • url_manage.py url管理器

build_url 构建初始网址
add_new_url 添加新的url到管理器
has_new_url 检查管理器里有没有新的url
get_new_url 从管理器里获取新的url
show_urls 遍历管理器里的url
这里的功能实际上都是针对的__init__里面设置的两个set,这里没有使用数据库,使用数据库也是一样的效果。

  def __init__(self):
        self.new_urls = set()
        self.old_urls = set()
        self.site = 'http://www.dhgate.com/w/{0}/{1}.html'
  • html_downloader.py 下载器

下载器其实很简单,只有一个方法,就是下载,这里直接引入requests包,使用其相关方法就完成了download方法。

  • html_parser.py 网页解析器

解析器是整个项目的核心,不过核心代码其实和第二次重构里差不多,基本上就是把第二次的核心代码挪过来就可以用了。

 def _get_new_data(self, page_url, soup):
        items = soup.find_all("div", "listitem")
        datas = []
        for item in items:
            title = item.find("h3").find("a").text  # 标题
            product_url = HtmlParser.format_str(
                item.select("h3 > a.subject")[0].get("href"))
            price = item.find("li", "price").text  # 价格
            min_price, max_price = re.findall(r'(\d+\.*\d+)', price)  # 最低价,最高价
            attribute = item.find("ul", "attribute").text
            min_order = re.findall(r'Min. Order: (\d+)', attribute)[0]  # 起订量
            order = re.findall(r'Sold: (\d+)', attribute)
            order = order[0] if len(order) > 0 else 0  # 订单量
            feedback = item.find("span", "reviewnum")
            feedback = re.findall(r"\d+",
                                  feedback.text)[0] if feedback else 0  # 产品好评
            seller = list(item.find("span",
                                    "seller").stripped_strings)[-1]  # 卖家
            store_url = item.find("span", "seller").find("a")['href']  # 店铺链接
            store_feedback = item.find("li", "feedback")
            store_feedback = re.findall(
                r"\d+\.*\d+",
                store_feedback.text)[0] if store_feedback else 0  # 店铺评价
            data = {
                'page_url': page_url,
                'title': title,
                'product_url': 'http:' + product_url,
                'min_price': min_price,
                'max_price': max_price,
                'min_order': min_order,
                'order': order,
                'feedback': feedback,
                'seller': seller,
                'store_url': store_url,
                'store_feedback': store_feedback
            }
            datas.append(data)
        return datas

就不多说了,parse方法里引用_get_new_data解析完成返回数据。

  • html_outputer.py 数据处理器
    其实这一个叫做数据处理器可能不太准确,叫做输出器可能更好,因为它的主要做用只是输出数据到本地存储。而且我们真正的数据处理其实都在解析器里已经完成了,大家可以看上面的代码。而且这里只有三个方法.

collect_data 收集前面处理好的数据
to_html 把数据输出成html格式
to_csv 把数据输出成csv格式,这个就和我们之前做的一样,不过这里使用了csv包,效率更高

    def to_html(self):
        with open('output.html', 'w') as f:
            f.write("<html>")
            f.write("<body>")
            f.write("<table>")
            f.write("<tr>")
            for key in self.datas[0].keys():
                f.write(f"<td>{key}</td>")
            f.write("</tr>")
            for data in self.datas:
                f.write("<tr>")
                for key, value in data.items():
                    f.write(f"<td>{value}</td>")
                f.write("</tr>")
            f.write("</table>")
            f.write("</body>")
            f.write("</html>")

    def to_csv(self, path="output.csv"):
        with open(path, 'w', newline="") as f:
            try:
                writer = csv.DictWriter(f, self.datas[0].keys())
            except IndexError:
                print(self.datas[0].keys())

            writer.writeheader()
            for data in self.datas:
                writer.writerow(data)

可以看到输出到html稍微麻烦点,主要是要写html特有的标签,而csv就相当简单了,csv包里有相应的方法,可以直接使用。

就这样,整体项目被我们用小框架实现了。

后记

下一篇,我们用python自带的图形库tk来实现一个界面,方便普通用法使用。大家记得关注我公众号,想要 源码可以在公众号后台输入 0020 获取。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 做淘宝真的要刷单么?那些所谓的七天螺旋,如果你一个新开的店,没权重没流量,不刷单哪来的螺旋给你? 淘宝大环境下,小...
    我是盼盼呢阅读 9,711评论 1 16
  • 读经: 《出埃及记》第11章。 经文: 摩西、亚伦在法老面前行了这一切奇事,耶和华使法老的心刚硬,不容以色列人出离...
    君自尔出阅读 7,726评论 2 4
  • 昨天一个偶然的机会,进驻到这里,没有大喜大悲,内心感慨万分。故事有很多,却不知道从何说起,所以打算以后常常打...
    更迭_71e3阅读 259评论 2 0
  • 昨天,帝都的几位朋友去了『知乎盐Club』,且狠狠地刷了我一脸屏。而我满怀着羡慕妒忌恨参加了Segmentfaul...
    鸡蛋碎花阅读 348评论 0 1