Delhi Art Gallery - Building Blocks

I always look for some kind of motivation or reason to build a project. Occasionally, I try to find a problem to solve on my own, even though there are already infinite tools available for it. But hey, it’s fun! It keeps me sane and creates a space where I can learn and have fun while doing it. It’s a great feeling—no matter how frustrating the process gets, the outcome brings a sense of satisfaction that gives me enough motivation to drag my ass and work on something new. And let’s be honest, there’s nothing quite like the thrill of reinventing the wheel just because you can!

I visited Delhi Art Gallery recently to see their new show Kali: Reverence & Rebellion which was curated by Gayatri Sinha.

Divided into sections, the exhibition traces Kali’s pervasive influence across the subcontinent. It explores Kali and her cohorts of the divine feminine. Born from Durga’s angry, darkened brow as she battles the asuras Chanda and Munda, Kali decapitates the demons and assumes the form of Chamunda. Within these depictions, she is seen alongside Durga, the primordial force, as well as the ten Mahavidyas that emerge to subdue Shiva.

A few weeks had passed, and I was having a conversation with a buddy of mine who works at some art gallery. He mentioned that this office is looking to hire someone who doesn’t need any training and already knows everything. The candidate should be self-sufficient because the person this hired candidate would be working under doesn’t like the idea of teaching. The term “teaching” covers a massive horizon, and you teach people and learn from people even when you don’t realize it. I have yet to meet anyone who knows everything about the job they’re doing and have stopped figuring out things as they move further in their life, but fair enough. It’s none of my business.

I don’t entirely remember the job description, but I do recall that it required maintaining Excel sheets of artworks in the warehouse. The candidate should be able to identify the artwork just by looking at it. Understandable, but to what extent? Are they expecting someone who can tell the difference between a genuine Monet and a decent forgery with a quick glance? My memory’s a bit fuzzy on the details, so I won’t spill my judgements.

Honestly, it somehow pissed me off a little, all because of this specific requirement: ‘The person should be able to look at the artwork and identify it immediately.’ And the entity is not willing to teach anything to the candidate because they expect this person to be a walking art encyclopedia.

So I was coming home from my morning walk and thought, if I have to test this person who doesn’t want to teach and wants to hire the Mozart of Art, what could I build to see if they can identify the artworks? It reminded me of the Delhi Art Gallery’s website, which has a good list of artists and their artworks, so I decided to build a simple quiz app. I didn’t build it to show anyone or prove anything to other people, and they wouldn’t have cared less about this basic thing, but I was positive that with enough tests and trials, even someone like me, who has absolutely zero knowledge of artworks, would be able to identify the artworks of specific artists, but I’ll have my own limits.

I think the absolute demand for specific skills is something that can be acquired if you train yourself on a specific dataset until you start to recognize patterns. However, you’ll be stumped if I end up showing you the artwork of an artist whose work you haven’t seen before. It’s not like these museums deal with millions of artworks, and you have to know about millions of artists. If these institutions care about quality, they’ll likely deal with popular artists or perhaps less-popular ones. These are two different spectrum.

But here’s the thing: the work of less-popular artists isn’t studied widely (or not studied at all), so how would I know it belongs to them just by looking at it? The world has enough data on popular artists and their artworks, and a week of dedicated training—even if you don’t know any artists or their artwork at the start—will give you reasonable exposure to start identifying artworks by looking at them, especially of the popular ones.


Screenshot of a random question on Quizart.

In the last 4-5 days, I have played this game so much that I’m now comfortable guessing. Absolute identification has yet to happen, but the ratio of getting the right answers has increased. I now know the names of some Indian artists, how their artwork looks, and a tiny bit about their lives. Kudos to DAG for putting together and maintaining structured information.

For example, the answer to the question in the above screenshot is — the second artwork. How do I know? Two reasons: I have played the game enough times, as I mentioned earlier, and second, while building the API and the game, I was also reading and looking for other artworks of the artists available on DAG’s site. Here’s a decent collection of Abdur Rahman Chughtai’s work.

There were a couple of reasons I chose DAG as a source for the data:

  1. Laziness — I didn’t want to scrape data from multiple sources as the idea wasn’t really that serious, and DAG had a decent list of Indian artists, so it was all I needed.
  2. Structure — As I said earlier, DAG has done a fabulous job in structuring the artists’ data, and once I figured out how to pull the data in order, I didn’t have many reasons to look elsewhere.

Limitations

You’ll find that there are fewer artworks available on an artist’s profile on DAG’s site. The reason for this is that, since it’s a gallery, they show only the artworks they sell. You can use the site for educational purposes, like looking at an artist’s timeline, their past exhibitions, some of their works, etc., but the number of artworks for a particular artist isn’t going to be extensive. In my case, the data was enough.

Look, I understand the so-called expertise that is required to do a job, but to be arrogant about your needs when you’re going to pay a low salary, and then you’re not willing to teach anything or provide guidance, yet you expect the candidate to have an inbuilt Google Lens so they can identify artworks — it doesn’t make sense. Even Google gives their employees enough time and training after they get selected, so the level of ego some people carry when it comes to hiring the perfect candidate is sometimes mind-boggling.


Building Blocks

Enough with the rant; let’s get back to business. The initial idea was to build a quiz app that shows some artworks and a question, and I can pick which artwork belongs to which artist. A fairly easy thing to do if you have the required data. Fortunately, DAG has really nice, neat, and organized data on artists, and most of them are Indian, which is a bonus point. So, I picked it as a source for collecting data. The game requires only a couple of things: the name of the artist and their associated artwork. That’s it, although the API I built covers a good portion of what’s available on DAG’s site.

Moreover, I’m not going to write code here, as the code is already available on GitHub.

View Source

There are two lists on the site — Featured Artists and All Artistslet’s call it Archive.

Thinking Thoughts

Due to the dynamic loading of the required content, I wrote a small Selenium script that would go to the Archive page, click on a letter, go to the artists’ profiles, and do the scraping. This was a pain due to its slow nature and having to restart the browser to test the script.

Since this was frustrating and extremely time-consuming, I decided to look for an alternative approach because there was no way I was going to use Selenium.

One thing I noticed is that when you open the page and it gets loaded, there aren’t any artists available initially, and then it makes a request again, and the artists under the category “A” become visible.

I opened the HTTP Header Live extension, refreshed the Archive page, cleared the initial log, and clicked on the letter “B” —

I hit the send button to make a POST request with a specific payload — category[]=66, and I received a JSON response containing HTML that dynamically generates a list of artists’ names starting with “B”.

In the payload, category[]=66 seemed like the ASCII for the letter “B”, which turned out to be the right guess when I checked for the artists starting with specific letters. The numbers in the payload were arranged as the ASCII representation of the character that the artists’ names begin with.

A = 65
B = 66
X = 89
Z = 90
$ curl -X POST https://dagworld.com/artist/index/view \
  -H "X-Requested-With: XMLHttpRequest" \
  -d "category[]=90" | jq
  1. X-Requested-With: XMLHttpRequest — Mandatory header as we need the server to recognize the request as an AJAX request; only then we’ll be able to get any response.
  2. category[]=90 — Get the list of artists whose names start with the letter “Z”.

complete response

{
  "output": "<div class=\"col-lg-5 artists-alphaList customScrollbar1\"> \n        <div id=\"Z\" class=\"container\">\n            <div class=\"row artists-list\">\n                <div class=\"col-1 p-0\">\n                    <p class=\"text2 text2Heading\">Z</p>\n                </div>\n                    <div class=\"col-11\">\n                                            <p class=\"text1\"><a class=\"artistLink\" href=\"https://dagworld.com/zarinahashmi.html\" data-img=\"https://d197irk3q85upd.cloudfront.net/catalog/product/cache/95dbdf78ad5a0d4b547132f80fbac8f3/z/a/zarinahashmi.jpg\" data-artist=\"Zarina Hashmi\">Zarina Hashmi</a></p>\n                                        </div>\n            </div>\n        </div>\n</div>\n<div class=\"col-md-7\">\n    <div class=\"artists-artCont\">\n        <img class=\"respImg\" src=\"https://d197irk3q85upd.cloudfront.net/catalog/product/cache/95dbdf78ad5a0d4b547132f80fbac8f3/z/a/zarinahashmi.jpg\">\n    </div>\n    <p id=\"artists-artTextCont\" class=\"text1 text-center mb-0\">Zarina Hashmi</p>\n</div>\n<span id=\"default_image\" style=\"display: none;\">https://d197irk3q85upd.cloudfront.net/catalog/product/cache/95dbdf78ad5a0d4b547132f80fbac8f3/z/a/zarinahashmi.jpg</span>\n<span id=\"default_name\" style=\"display: none;\">Zarina Hashmi</span>\n<script type=\"text/javascript\">\nrequire(['jquery'], function($) {\n    var default_image = $(\"#default_image\").html();\n    var default_name = $(\"#default_name\").html();\n    $('.artistLink').hover(\n                function(){\n                    $(this).addClass('hover');\n                    $('.artists-artCont img').attr('src', $(this).data('img'));\n                    $('#artists-artTextCont').html($(this).data('artist'));\n                },\n                function(){\n                    $(this).removeClass('hover');\n                    // $('.artists-artCont img').attr('src', default_image);\n                    // $('#artists-artTextCont').html(default_name);\n                }\n            );            \n});\n</script>"
}

what we sort of looking for

<div class=col-11>
  <p class=text1>
    <a class=artistLink href=https://dagworld.com/zarinahashmi.html data-img=https://d197irk3q85upd.cloudfront.net/catalog/product/cache/95dbdf78ad5a0d4b547132f80fbac8f3/z/a/zarinahashmi.jpg data-artist=Zarina Hashmi>Zarina Hashmi</a></p>
</div>

Phase 1 — Artist Profile

From the response I was receiving, I needed to organize everything in two ways:

base_url = 'https://dagworld.com/artist/index/view'

for char in string.ascii_uppercase:
    payload = {'category[]': ord(char)}
    response = requests.post(base_url, data=payload, headers=headers)
    # rest of the processing

Stage 1 — Sample Output

➜  Artists ls
A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z
➜  Artists tree B J
B
├── Baburao_Painter_Artist.json
├── Badri_Narayan_Artist.json
├── B._Prabha_Artist.json
└── B._Vithal_Artist.json
J
├── Jagadish_Dey_Artist.json
├── Jagmohan_Chopra_Artist.json
├── J._Swaminathan_Artist.json
└── Jyoti_Bhatt_Artist.json

Initial JSON structure

➜  M cat M._F._Husain_Artist.json | jq
{
  "profile": "https://dagworld.com/m.f.husain.html",
  "image": "https://d197irk3q85upd.cloudfront.net/catalog/product/cache/95dbdf78ad5a0d4b547132f80fbac8f3/m/f/mf_husain_cover.jpg"
}

Phase 2 — Extending Artist Profiles

Now that I had the complete list of artists with their profiles organized, it was time to add another layer of data associated with them. I intentionally avoided scraping everything all at once, so I decided to scrape the basic details like name, short biography, introduction, and birth/death year.

I wrote another script to scrape the basic details. It was fairly fast as well —

Results —

currProId is something that will be used to scrap the artworks of an artist.

At this point, everything was working perfectly. I checked a few of the files to see if they contained the data I was scraping, but then I thought, what if some fields are missing? So, I wrote another script, and indeed I was right -

I reran the original script to see if it fixed the issue, but it didn’t, and those four files were the only ones with missing bio fields both times. I tried to make some changes to the script to fix it, but I wasn’t really into it due to the problematic files being few in number. I didn’t spend much time on this issue, nor did I want to find a solution. I just copied their bios from DAG’s site and pasted them into their respective JSON files. That’s it. This phase was done, and I had well-structured data on the artists.

Phase 3 — Adding Artworks

The method used to load the list of artists (via client-side requests to dynamically generate the list) was identical to the way artworks were being loaded. During the initial load, it would fetch the basic details of the artists, followed by an additional request to load the artworks.

Upon inspecting M.F Husain’s profile -

curl 'https://dagworld.com/allartworks/index/view/' \
  -H 'Origin: https://dagworld.com' \
  -H 'Referer: https://dagworld.com/m.f.husain.html' \
  -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' \
  -H 'X-Requested-With: XMLHttpRequest' \
  --data-raw 'currProId=2161&currPage=1&lastArtProId=5872'

Raw Output (Sample)

{"output":" <script>\nrequire(['jquery']...function($)<\/div>\n\t\t\t\t\t<\/div>"}

Clean Output (Sample)

<div class="col-md-4 main-column left-column">
    <div class="work-cont work-contShow wow fadeInUp" data-wow-duration="2s">
        <a href="https://dagworld.com/code-017c.html">
            <div class="artwork-imgWork">
                <table style="width:100%;height:100%;">
                    <tr>
                        <td class="align-middle text-center" style="width:100%;height:100%;">
                            <div class="mb-hover-icons">
                                <a href="">
                                    <img src="https://d197irk3q85upd.cloudfront.net/icons/plus.png">
                                </a>
                            </div>
                            <img class="respImg" src="https://d197irk3q85upd.cloudfront.net/catalog/product/cache/95dbdf78ad5a0d4b547132f80fbac8f3/h/u/husainmf_017c.jpg" />
                        </td>
                    </tr>
                </table>
            </div>
        </a>
        <div class="text-cont">
            <div class="row">
                <div class="col-lg-10 col-md-12 col-10">
                    <p class="text1 italic mb-0">Untitled</p>
                    <p class="text1 mb-0"></p>
                    <p class="text1 mb-0">Acrylic on paper</p>
                </div>
                <div class="col-lg-2 col-md-12 col-2">
                    <p class="text1 bold mb-0">SOLD</p>
                </div>
            </div>
        </div>
    </div>
</div>

Required data

Artwork <img class="respImg" src="husainmf_017c.jpg" />
Title <p class="text1 italic mb-0">Untitled</p>
Year <p class="text1 mb-0"></p>
Medium <p class="text1 mb-0">Acrylic on paper</p>

Extraction —

I wrote another script that goes through all the files present in Artists/A...Z, grabs the artist’s profile from the profile field and currProId from currentProductId, and makes the POST request to https://dagworld/allartworks/index.view/ with the relevant headers. In the original script, I’ve used other fields and a bunch of headers, but upon inspecting, I found out that only currProId was needed, or maybe the value associated with Page= if I need to grab more artworks of the artist. For headers, it only needed Origin and X-Requested-With.

Phase 4 — API and Endpoints

I decided to use MongoDB to host the JSON file containing all the data of the artist. For that, I first had to merge them into one file, which was a fairly straightforward task. I then started building the API and decided to include a few extra endpoints, not all of which were required for my quiz game. However, since I was building the API, I thought, why not make it a bit flexible — something that can also be used to interact with the content available on DAG’s site.

{
    "name": "Jagadish Dey",
    "year": "b - 1942",
    "intro": "Born...2000.",
    "CurrentProductId": "2130",
    "LastArtProId": "5852",
    "profile": "https://dagworld.com/jagadishdey.html",
    "image": "https://d197irk3q85upd...jagdish_dey_image.jpg",
    "artworks": [
        {
            "painting": "https://d197irk3q85upd...deyjagadish24.jpg",
            "title": "Flight",
            "year": 2004,
            "medium": "Acrylic on canvas"
        }
    ]
}
{
    "name": "Jaya Ganguly",
    "year": "b - 1958",
    "intro": "Jaya...Kolkata (1997).",
    "CurrentProductId": "2134",
    "LastArtProId": "3183",
    "profile": "https://dagworld.com/jayaganguly.html",
    "image": "https://d197irk3q85upd...jaya_ganguly.jpg",
    "artworks": [
        {
            "painting": "https://d197irk3q85upd.../gangulyj002.jpg",
            "title": "Four Figures",
            "year": 1982,
            "medium": "Chinese ink and watercolour on paper pasted on Masonite board"
        }
    ]
}

Endpoints

/artists https://dagworld.vercel.app/artists
/artists/random https://dagworld.vercel.app/artists/random
/artists/random/{number} https://dagworld.vercel.app/artists/random/5
/artist/{artist_name} https://dagworld.vercel.app/artist/Jagadish Dey
/artist/{artist_name}/intro https://dagworld.vercel.app/artist/M. F. Husain/intro
/artist/{artist_name}/bio https://dagworld.vercel.app/artist/Himmat Shah/bio
/artist/{artist_name}/year https://dagworld.vercel.app/artist/Asit Kumar Haldar/year
/artist/{artist_name}/image https://dagworld.vercel.app/artist/Krishen Khanna/image
/artist/{artist_name}/artworks https://dagworld.vercel.app/artist/Prabhakar Barwe/artworks
/artists/search/{query} https://dagworld.vercel.app/artists/search/Jogen Chowdhury

Phase 5 — Building Game

Future Tasks

A couple of things that need to be done:

I’m quite done with the project, so I don’t know when I’m going to do all these things. Hopefully, someday I’ll get bored and come back to it, but let’s see when that happens.


Sum Up / Sum Down

I started this project because I was frustrated with something I had no business getting involved in, but I also ended up learning a lot. In hindsight, that frustration turned out to be not-so-bad after all.

😺 Meow Meow 🐱