mirror of
https://github.com/bellingcat/instagram-location-search.git
synced 2026-06-08 02:28:29 +03:00
Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
|
||||
27
README.md
Normal file
27
README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Instagram Location Search
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This Python application requires `requests` and `numpy` to be properly installed.
|
||||
|
||||
## Example usage
|
||||
|
||||
The following command will search for Instagram locations nearby the coordinates 32.22 N, 110.97 W (downtown Tucson, Arizona.) The list of locations is saved as a JSON file at "locs.json".
|
||||
|
||||
```python3 instagram-locations.py --session "<session-id-token>" --lat 32.22 --lng -110.97 --json locs.json```
|
||||
|
||||
Note that this requires an Instagram session ID in order to work! See below for how to obtain one from your account.
|
||||
|
||||
### Other output formats
|
||||
|
||||
Using the `--geojson <output-location>` command line argument, the list can be saved as a GeoJSON file for other geospatial applications.
|
||||
|
||||
Using the `--map <output-location>` command line argument, a simple Leaflet map is made to visualize the locations of the returned points.
|
||||
|
||||
![docs/map-example.png]
|
||||
|
||||
Multiple types of output can be generated. For example, the following command will search for Instagram locations, save the JSON list, a GeoJSON file, and a map for viewing the locations visually.
|
||||
|
||||
```python3 instagram-locations.py --session "3888090946%3AhdKd2fA8d72dqD%3A16" --lat 32.22 --lng -110.97 --json locs.json --geojson locs.geojson --map map.html```
|
||||
|
||||
## Getting an Instagram session ID
|
||||
BIN
docs/map-example.png
Normal file
BIN
docs/map-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
145
instagram-locations.py
Normal file
145
instagram-locations.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import requests
|
||||
import numpy as np
|
||||
import argparse
|
||||
import json
|
||||
from string import Template
|
||||
|
||||
# gets instagram "locations" around a particular lat/lng using internal API
|
||||
# (requires session cookie for authentication)
|
||||
def get_instagram_locations(lat, lng, cookie):
|
||||
locs = requests.get("https://www.instagram.com/location_search/?latitude=" + str(lat) + "&longitude=" + str(lng) + "&__a=1", headers={
|
||||
'Cookie': cookie
|
||||
}).json()
|
||||
|
||||
return locs['venues']
|
||||
|
||||
|
||||
def get_instagram_locations_by_query(query):
|
||||
locs = requests.get("https://www.instagram.com/web/search/topsearch/?context=place&query=" + query).json()
|
||||
|
||||
return [v['place']['location'] for v in locs['places']]
|
||||
|
||||
# queries the instagram location API for several points around a central lat/lng
|
||||
# in order to return additional results
|
||||
def get_fuzzy_locations(lat, lng, cookie, sigma=2):
|
||||
locs = get_instagram_locations(lat, lng, cookie)
|
||||
|
||||
std_lat = np.std([v['lat'] for v in locs if 'lat' in v])
|
||||
std_lng = np.std([v['lng'] for v in locs if 'lng' in v])
|
||||
|
||||
for delta_lat in range(-sigma, sigma+1):
|
||||
for delta_lng in range(-sigma, sigma+1):
|
||||
new_locs = get_instagram_locations(lat + delta_lat * std_lat, lng + delta_lng * std_lng, cookie)
|
||||
loc_ids = [v['external_id'] for v in locs]
|
||||
|
||||
for loc in new_locs:
|
||||
if loc['external_id'] not in loc_ids:
|
||||
locs.append(loc)
|
||||
|
||||
return locs
|
||||
|
||||
# converts list of instagram locations into valid geojson
|
||||
def make_geojson(locs):
|
||||
features = []
|
||||
|
||||
for l in [l for l in locs if 'lng' in l]:
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [l["lng"], l["lat"]]
|
||||
},
|
||||
"properties": l}
|
||||
features.append(feature)
|
||||
|
||||
return {"type": "FeatureCollection", "features": features}
|
||||
|
||||
html_template = '''<html>
|
||||
<head>
|
||||
<title>Instagram location visualizations</title>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
|
||||
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
|
||||
crossorigin=""
|
||||
/>
|
||||
<script
|
||||
src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
|
||||
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
|
||||
crossorigin=""
|
||||
></script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
|
||||
<script>
|
||||
var map = L.map("map").setView([$lat, $lng], 14);
|
||||
|
||||
var locs = $locs;
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 18,
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
id: "mapbox/light-v9",
|
||||
}).addTo(map);
|
||||
|
||||
function onEachFeature(feature, layer) {
|
||||
layer.bindPopup(`<a href="https://www.instagram.com/explore/locations/` + feature.properties.external_id + `">` + feature.properties.name + `</a><br />` + feature.properties.address );
|
||||
}
|
||||
|
||||
L.geoJSON(locs, {
|
||||
onEachFeature: onEachFeature
|
||||
}).addTo(map);
|
||||
</script>
|
||||
</body>
|
||||
</html>'''
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Get a list of Instagram locations near a lat/lng")
|
||||
parser.add_argument("--session", action="store", dest="session")
|
||||
parser.add_argument("--json", action="store", dest="output")
|
||||
parser.add_argument("--geojson", action="store", dest="geojson")
|
||||
parser.add_argument("--map", action="store", dest="map")
|
||||
parser.add_argument("--lat", action="store", dest="lat")
|
||||
parser.add_argument("--lng", action="store", dest="lng")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
cookie = 'sessionid=' + args.session
|
||||
|
||||
locations = get_fuzzy_locations(float(args.lat), float(args.lng), cookie)
|
||||
|
||||
if (args.output):
|
||||
json.dump(locations, open(args.output, 'w'))
|
||||
|
||||
if (args.geojson):
|
||||
json.dump(make_geojson(locations), open(args.geojson, 'w'))
|
||||
|
||||
if (args.map):
|
||||
s = Template(html_template)
|
||||
viz = s.substitute(lat=args.lat, lng=args.lng, locs=json.dumps(make_geojson(locations)))
|
||||
|
||||
f = open(args.map, 'w')
|
||||
f.write(viz)
|
||||
f.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user